diff --git a/404.html b/404.html index 5f7ad2c44..97fba48bb 100644 --- a/404.html +++ b/404.html @@ -24,7 +24,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/index.html b/articles/index.html index 613a67da9..f340e7127 100644 --- a/articles/index.html +++ b/articles/index.html @@ -10,7 +10,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/model-description.html b/articles/model-description.html index 2967bf695..0eee77f3d 100644 --- a/articles/model-description.html +++ b/articles/model-description.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/basic-intro.html b/articles/web_only/basic-intro.html index f0e0c97a3..54c40c3b9 100644 --- a/articles/web_only/basic-intro.html +++ b/articles/web_only/basic-intro.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Introduction to modelling with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/basic-intro.Rmd basic-intro.Rmd @@ -186,11 +186,12 @@ 2024-05-08 #> #> ML criterion at convergence: 1193.035 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. -AIC(m) +#> See ?tidy.sdmTMB to extract these values as a data frame. + +AIC(m) #> [1] 2392.07 For comparison, here’s the same model with glm(): - + m0 <- glm( data = pcod, formula = present ~ depth_scaled + depth_scaled2, @@ -222,7 +223,7 @@ 2024-05-08 Next, we can incorporate spatial random effects into the above model by changing spatial to "on" and see that this changes coefficient estimates: - + m1 <- sdmTMB( data = pcod, formula = present ~ depth_scaled + depth_scaled2, @@ -246,8 +247,9 @@ 2024-05-08 #> Spatial SD: 1.65 #> ML criterion at convergence: 1042.157 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. -AIC(m1) +#> See ?tidy.sdmTMB to extract these values as a data frame. + +AIC(m1) #> [1] 2094.314 To add spatiotemporal random fields to this model, we need to include both the time argument that indicates what column of your data frame @@ -258,7 +260,7 @@ 2024-05-08 (spatiotemporal = "AR1"), or as a random walk (spatiotemporal = "RW"). We will stick with IID for these examples. - + m2 <- sdmTMB( data = pcod, formula = present ~ depth_scaled + depth_scaled2, @@ -290,7 +292,7 @@ 2024-05-08 We can also model biomass density using a Tweedie distribution. We’ll switch to poly() notation to make some of the plotting easier. - + m3 <- sdmTMB( data = pcod, formula = density ~ poly(log(depth), 2), @@ -326,7 +328,7 @@ Parameter estimates + tidy(m3, conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high @@ -335,7 +337,7 @@ Parameter estimates#> 2 poly(log(depth), 2)1 -65.1 6.32 -77.5 -52.8 #> 3 poly(log(depth), 2)2 -96.5 5.98 -108. -84.8 And similarly for the random effect and variance parameters: - + tidy(m3, "ran_pars", conf.int = TRUE) #> # A tibble: 5 × 5 #> term estimate std.error conf.low conf.high @@ -376,16 +378,17 @@ Parameter estimatesModel diagnostics We can inspect randomized quantile residuals: - + pcod$resids <- residuals(m3) # randomized quantile residuals #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -qqnorm(pcod$resids) +#> `dharma_residuals()` using a similar approach. + +qqnorm(pcod$resids) qqline(pcod$resids) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + @@ -400,14 +403,14 @@ Model diagnostics + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1: + +r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r) @@ -450,7 +454,7 @@ Spatial predictions + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
vignettes/web_only/basic-intro.Rmd
basic-intro.Rmd
+AIC(m) #> [1] 2392.07
AIC(m) #> [1] 2392.07
For comparison, here’s the same model with glm():
glm()
+ m0 <- glm( data = pcod, formula = present ~ depth_scaled + depth_scaled2, @@ -222,7 +223,7 @@ 2024-05-08 Next, we can incorporate spatial random effects into the above model by changing spatial to "on" and see that this changes coefficient estimates: - + m1 <- sdmTMB( data = pcod, formula = present ~ depth_scaled + depth_scaled2, @@ -246,8 +247,9 @@ 2024-05-08 #> Spatial SD: 1.65 #> ML criterion at convergence: 1042.157 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. -AIC(m1) +#> See ?tidy.sdmTMB to extract these values as a data frame. + +AIC(m1) #> [1] 2094.314 To add spatiotemporal random fields to this model, we need to include both the time argument that indicates what column of your data frame @@ -258,7 +260,7 @@ 2024-05-08 (spatiotemporal = "AR1"), or as a random walk (spatiotemporal = "RW"). We will stick with IID for these examples. - + m2 <- sdmTMB( data = pcod, formula = present ~ depth_scaled + depth_scaled2, @@ -290,7 +292,7 @@ 2024-05-08 We can also model biomass density using a Tweedie distribution. We’ll switch to poly() notation to make some of the plotting easier. - + m3 <- sdmTMB( data = pcod, formula = density ~ poly(log(depth), 2), @@ -326,7 +328,7 @@ Parameter estimates + tidy(m3, conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high @@ -335,7 +337,7 @@ Parameter estimates#> 2 poly(log(depth), 2)1 -65.1 6.32 -77.5 -52.8 #> 3 poly(log(depth), 2)2 -96.5 5.98 -108. -84.8 And similarly for the random effect and variance parameters: - + tidy(m3, "ran_pars", conf.int = TRUE) #> # A tibble: 5 × 5 #> term estimate std.error conf.low conf.high @@ -376,16 +378,17 @@ Parameter estimatesModel diagnostics We can inspect randomized quantile residuals: - + pcod$resids <- residuals(m3) # randomized quantile residuals #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -qqnorm(pcod$resids) +#> `dharma_residuals()` using a similar approach. + +qqnorm(pcod$resids) qqline(pcod$resids) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + @@ -400,14 +403,14 @@ Model diagnostics + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1: + +r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r) @@ -450,7 +454,7 @@ Spatial predictions + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
m0 <- glm( data = pcod, formula = present ~ depth_scaled + depth_scaled2, @@ -222,7 +223,7 @@ 2024-05-08 Next, we can incorporate spatial random effects into the above model by changing spatial to "on" and see that this changes coefficient estimates: - + m1 <- sdmTMB( data = pcod, formula = present ~ depth_scaled + depth_scaled2, @@ -246,8 +247,9 @@ 2024-05-08 #> Spatial SD: 1.65 #> ML criterion at convergence: 1042.157 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. -AIC(m1) +#> See ?tidy.sdmTMB to extract these values as a data frame. + +AIC(m1) #> [1] 2094.314 To add spatiotemporal random fields to this model, we need to include both the time argument that indicates what column of your data frame @@ -258,7 +260,7 @@ 2024-05-08 (spatiotemporal = "AR1"), or as a random walk (spatiotemporal = "RW"). We will stick with IID for these examples. - + m2 <- sdmTMB( data = pcod, formula = present ~ depth_scaled + depth_scaled2, @@ -290,7 +292,7 @@ 2024-05-08 We can also model biomass density using a Tweedie distribution. We’ll switch to poly() notation to make some of the plotting easier. - + m3 <- sdmTMB( data = pcod, formula = density ~ poly(log(depth), 2), @@ -326,7 +328,7 @@ Parameter estimates + tidy(m3, conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high @@ -335,7 +337,7 @@ Parameter estimates#> 2 poly(log(depth), 2)1 -65.1 6.32 -77.5 -52.8 #> 3 poly(log(depth), 2)2 -96.5 5.98 -108. -84.8 And similarly for the random effect and variance parameters: - + tidy(m3, "ran_pars", conf.int = TRUE) #> # A tibble: 5 × 5 #> term estimate std.error conf.low conf.high @@ -376,16 +378,17 @@ Parameter estimatesModel diagnostics We can inspect randomized quantile residuals: - + pcod$resids <- residuals(m3) # randomized quantile residuals #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -qqnorm(pcod$resids) +#> `dharma_residuals()` using a similar approach. + +qqnorm(pcod$resids) qqline(pcod$resids) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + @@ -400,14 +403,14 @@ Model diagnostics + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1: + +r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r) @@ -450,7 +454,7 @@ Spatial predictions + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
Next, we can incorporate spatial random effects into the above model by changing spatial to "on" and see that this changes coefficient estimates:
spatial
"on"
+ m1 <- sdmTMB( data = pcod, formula = present ~ depth_scaled + depth_scaled2, @@ -246,8 +247,9 @@ 2024-05-08 #> Spatial SD: 1.65 #> ML criterion at convergence: 1042.157 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. -AIC(m1) +#> See ?tidy.sdmTMB to extract these values as a data frame. + +AIC(m1) #> [1] 2094.314 To add spatiotemporal random fields to this model, we need to include both the time argument that indicates what column of your data frame @@ -258,7 +260,7 @@ 2024-05-08 (spatiotemporal = "AR1"), or as a random walk (spatiotemporal = "RW"). We will stick with IID for these examples. - + m2 <- sdmTMB( data = pcod, formula = present ~ depth_scaled + depth_scaled2, @@ -290,7 +292,7 @@ 2024-05-08 We can also model biomass density using a Tweedie distribution. We’ll switch to poly() notation to make some of the plotting easier. - + m3 <- sdmTMB( data = pcod, formula = density ~ poly(log(depth), 2), @@ -326,7 +328,7 @@ Parameter estimates + tidy(m3, conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high @@ -335,7 +337,7 @@ Parameter estimates#> 2 poly(log(depth), 2)1 -65.1 6.32 -77.5 -52.8 #> 3 poly(log(depth), 2)2 -96.5 5.98 -108. -84.8 And similarly for the random effect and variance parameters: - + tidy(m3, "ran_pars", conf.int = TRUE) #> # A tibble: 5 × 5 #> term estimate std.error conf.low conf.high @@ -376,16 +378,17 @@ Parameter estimatesModel diagnostics We can inspect randomized quantile residuals: - + pcod$resids <- residuals(m3) # randomized quantile residuals #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -qqnorm(pcod$resids) +#> `dharma_residuals()` using a similar approach. + +qqnorm(pcod$resids) qqline(pcod$resids) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + @@ -400,14 +403,14 @@ Model diagnostics + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1: + +r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r) @@ -450,7 +454,7 @@ Spatial predictions + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
m1 <- sdmTMB( data = pcod, formula = present ~ depth_scaled + depth_scaled2, @@ -246,8 +247,9 @@ 2024-05-08 #> Spatial SD: 1.65 #> ML criterion at convergence: 1042.157 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. -AIC(m1) +#> See ?tidy.sdmTMB to extract these values as a data frame.
+AIC(m1) #> [1] 2094.314
AIC(m1) #> [1] 2094.314
To add spatiotemporal random fields to this model, we need to include both the time argument that indicates what column of your data frame @@ -258,7 +260,7 @@
spatiotemporal = "AR1"
spatiotemporal = "RW"
+ m2 <- sdmTMB( data = pcod, formula = present ~ depth_scaled + depth_scaled2, @@ -290,7 +292,7 @@ 2024-05-08 We can also model biomass density using a Tweedie distribution. We’ll switch to poly() notation to make some of the plotting easier. - + m3 <- sdmTMB( data = pcod, formula = density ~ poly(log(depth), 2), @@ -326,7 +328,7 @@ Parameter estimates + tidy(m3, conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high @@ -335,7 +337,7 @@ Parameter estimates#> 2 poly(log(depth), 2)1 -65.1 6.32 -77.5 -52.8 #> 3 poly(log(depth), 2)2 -96.5 5.98 -108. -84.8 And similarly for the random effect and variance parameters: - + tidy(m3, "ran_pars", conf.int = TRUE) #> # A tibble: 5 × 5 #> term estimate std.error conf.low conf.high @@ -376,16 +378,17 @@ Parameter estimatesModel diagnostics We can inspect randomized quantile residuals: - + pcod$resids <- residuals(m3) # randomized quantile residuals #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -qqnorm(pcod$resids) +#> `dharma_residuals()` using a similar approach. + +qqnorm(pcod$resids) qqline(pcod$resids) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + @@ -400,14 +403,14 @@ Model diagnostics + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1: + +r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r) @@ -450,7 +454,7 @@ Spatial predictions + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
m2 <- sdmTMB( data = pcod, formula = present ~ depth_scaled + depth_scaled2, @@ -290,7 +292,7 @@ 2024-05-08 We can also model biomass density using a Tweedie distribution. We’ll switch to poly() notation to make some of the plotting easier. - + m3 <- sdmTMB( data = pcod, formula = density ~ poly(log(depth), 2), @@ -326,7 +328,7 @@ Parameter estimates + tidy(m3, conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high @@ -335,7 +337,7 @@ Parameter estimates#> 2 poly(log(depth), 2)1 -65.1 6.32 -77.5 -52.8 #> 3 poly(log(depth), 2)2 -96.5 5.98 -108. -84.8 And similarly for the random effect and variance parameters: - + tidy(m3, "ran_pars", conf.int = TRUE) #> # A tibble: 5 × 5 #> term estimate std.error conf.low conf.high @@ -376,16 +378,17 @@ Parameter estimatesModel diagnostics We can inspect randomized quantile residuals: - + pcod$resids <- residuals(m3) # randomized quantile residuals #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -qqnorm(pcod$resids) +#> `dharma_residuals()` using a similar approach. + +qqnorm(pcod$resids) qqline(pcod$resids) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + @@ -400,14 +403,14 @@ Model diagnostics + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1: + +r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r) @@ -450,7 +454,7 @@ Spatial predictions + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
We can also model biomass density using a Tweedie distribution. We’ll switch to poly() notation to make some of the plotting easier.
poly()
+ m3 <- sdmTMB( data = pcod, formula = density ~ poly(log(depth), 2), @@ -326,7 +328,7 @@ Parameter estimates + tidy(m3, conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high @@ -335,7 +337,7 @@ Parameter estimates#> 2 poly(log(depth), 2)1 -65.1 6.32 -77.5 -52.8 #> 3 poly(log(depth), 2)2 -96.5 5.98 -108. -84.8 And similarly for the random effect and variance parameters: - + tidy(m3, "ran_pars", conf.int = TRUE) #> # A tibble: 5 × 5 #> term estimate std.error conf.low conf.high @@ -376,16 +378,17 @@ Parameter estimatesModel diagnostics We can inspect randomized quantile residuals: - + pcod$resids <- residuals(m3) # randomized quantile residuals #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -qqnorm(pcod$resids) +#> `dharma_residuals()` using a similar approach. + +qqnorm(pcod$resids) qqline(pcod$resids) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + @@ -400,14 +403,14 @@ Model diagnostics + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1: + +r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r) @@ -450,7 +454,7 @@ Spatial predictions + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
m3 <- sdmTMB( data = pcod, formula = density ~ poly(log(depth), 2), @@ -326,7 +328,7 @@ Parameter estimates + tidy(m3, conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high @@ -335,7 +337,7 @@ Parameter estimates#> 2 poly(log(depth), 2)1 -65.1 6.32 -77.5 -52.8 #> 3 poly(log(depth), 2)2 -96.5 5.98 -108. -84.8 And similarly for the random effect and variance parameters: - + tidy(m3, "ran_pars", conf.int = TRUE) #> # A tibble: 5 × 5 #> term estimate std.error conf.low conf.high @@ -376,16 +378,17 @@ Parameter estimatesModel diagnostics We can inspect randomized quantile residuals: - + pcod$resids <- residuals(m3) # randomized quantile residuals #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -qqnorm(pcod$resids) +#> `dharma_residuals()` using a similar approach. + +qqnorm(pcod$resids) qqline(pcod$resids) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + @@ -400,14 +403,14 @@ Model diagnostics + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1: + +r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r) @@ -450,7 +454,7 @@ Spatial predictions + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
+ tidy(m3, conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high @@ -335,7 +337,7 @@ Parameter estimates#> 2 poly(log(depth), 2)1 -65.1 6.32 -77.5 -52.8 #> 3 poly(log(depth), 2)2 -96.5 5.98 -108. -84.8 And similarly for the random effect and variance parameters: - + tidy(m3, "ran_pars", conf.int = TRUE) #> # A tibble: 5 × 5 #> term estimate std.error conf.low conf.high @@ -376,16 +378,17 @@ Parameter estimatesModel diagnostics We can inspect randomized quantile residuals: - + pcod$resids <- residuals(m3) # randomized quantile residuals #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -qqnorm(pcod$resids) +#> `dharma_residuals()` using a similar approach. + +qqnorm(pcod$resids) qqline(pcod$resids) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + @@ -400,14 +403,14 @@ Model diagnostics + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1: + +r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r) @@ -450,7 +454,7 @@ Spatial predictions + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
tidy(m3, conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high @@ -335,7 +337,7 @@ Parameter estimates#> 2 poly(log(depth), 2)1 -65.1 6.32 -77.5 -52.8 #> 3 poly(log(depth), 2)2 -96.5 5.98 -108. -84.8
And similarly for the random effect and variance parameters:
+ tidy(m3, "ran_pars", conf.int = TRUE) #> # A tibble: 5 × 5 #> term estimate std.error conf.low conf.high @@ -376,16 +378,17 @@ Parameter estimatesModel diagnostics We can inspect randomized quantile residuals: - + pcod$resids <- residuals(m3) # randomized quantile residuals #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -qqnorm(pcod$resids) +#> `dharma_residuals()` using a similar approach. + +qqnorm(pcod$resids) qqline(pcod$resids) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + @@ -400,14 +403,14 @@ Model diagnostics + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1: + +r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r) @@ -450,7 +454,7 @@ Spatial predictions + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
tidy(m3, "ran_pars", conf.int = TRUE) #> # A tibble: 5 × 5 #> term estimate std.error conf.low conf.high @@ -376,16 +378,17 @@ Parameter estimatesModel diagnostics We can inspect randomized quantile residuals: - + pcod$resids <- residuals(m3) # randomized quantile residuals #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -qqnorm(pcod$resids) +#> `dharma_residuals()` using a similar approach. + +qqnorm(pcod$resids) qqline(pcod$resids) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + @@ -400,14 +403,14 @@ Model diagnostics + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1: + +r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r) @@ -450,7 +454,7 @@ Spatial predictions + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
We can inspect randomized quantile residuals:
+ pcod$resids <- residuals(m3) # randomized quantile residuals #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -qqnorm(pcod$resids) +#> `dharma_residuals()` using a similar approach. + +qqnorm(pcod$resids) qqline(pcod$resids) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + @@ -400,14 +403,14 @@ Model diagnostics + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1: + +r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r) @@ -450,7 +454,7 @@ Spatial predictions + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
pcod$resids <- residuals(m3) # randomized quantile residuals #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -qqnorm(pcod$resids) +#> `dharma_residuals()` using a similar approach.
+qqnorm(pcod$resids) qqline(pcod$resids)
qqnorm(pcod$resids) qqline(pcod$resids)
+ ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + @@ -400,14 +403,14 @@ Model diagnostics + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1: + +r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r) @@ -450,7 +454,7 @@ Spatial predictions + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + @@ -400,14 +403,14 @@ Model diagnostics + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1: + +r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r) @@ -450,7 +454,7 @@ Spatial predictions + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
+ set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1: + +r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r) @@ -450,7 +454,7 @@ Spatial predictions + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(m3, mcmc_warmup = 100, mcmc_iter = 101) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.010393 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 103.93 seconds. +#> Chain 1: Gradient evaluation took 0.010134 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 101.34 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -432,11 +435,12 @@ Model diagnostics#> Chain 1: Iteration: 100 / 101 [ 99%] (Warmup) #> Chain 1: Iteration: 101 / 101 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 26.68 seconds (Warm-up) -#> Chain 1: 0.202 seconds (Sampling) -#> Chain 1: 26.882 seconds (Total) -#> Chain 1: -r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 26.274 seconds (Warm-up) +#> Chain 1: 0.2 seconds (Sampling) +#> Chain 1: 26.474 seconds (Total) +#> Chain 1:
+r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r)
r <- residuals(m3, "mle-mcmc", mcmc_samples = samps) qqnorm(r) qqline(r)
+ glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all necessary years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) + Posterior predictive checks @@ -282,16 +438,17 @@ Posterior predictive checkssimulate.sdmTMB() will take draws from the joint parameter posterior and add observation error. We need to ensure nsim is less than or equal to the total number of post-warmup samples. - + set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -460,13 +464,13 @@ Spatial predictions#> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01…
We can replicate our grid across all necessary years:
+ grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now we will make the predictions on new data: - + predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot) + + +bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +
grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year))
Now we will make the predictions on new data:
+ predictions <- predict(m3, newdata = grid_yrs) Let’s make a small function to help make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals) + + +pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot) +bayesplot::mcmc_trace(fit_stan, pars = pars_plot)
predictions <- predict(m3, newdata = grid_yrs)
Let’s make a small function to help make maps.
+ plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1). The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@ Passing the model to tmbstan plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals)
plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -475,7 +479,7 @@ Spatial predictions + plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1).
+ plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa` Passing the model to tmbstan @@ -256,7 +304,107 @@ Passing the model to tmbstan -fit_stan +fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1).
plot_map(predictions, exp(est)) + scale_fill_viridis_c( trans = "sqrt", @@ -489,7 +493,7 @@ Spatial predictions We can also look at just the fixed effects, here only a quadratic effect of depth: - + plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa`
We can also look at just the fixed effects, here only a quadratic effect of depth:
+ plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)") @@ -499,7 +503,7 @@ Spatial predictions + plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa`
plot_map(predictions, exp(est_non_rf)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects only)")
+ plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only") @@ -509,7 +513,7 @@ Spatial predictions + plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa`
plot_map(predictions, omega_s) + scale_fill_gradient2() + ggtitle("Spatial random effects only")
+ plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Fixing a spatial correlation parameter to improve convergence @@ -229,7 +274,10 @@ Fixing a ) ), do_fit = FALSE #< -) +) +#> ℹ Initiating `ln_kappa` at specified starting value(s) of: +#> 2.173, 2.173 +#> ℹ Fixing or mirroring `ln_kappa`
plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply() to calculate upper and lower confidence intervals, a standard deviation, and a coefficient of variation (CV). - + sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame.
plot_map(predictions, epsilon_st) + scale_fill_gradient2() + facet_wrap(~year) + @@ -521,7 +525,7 @@ Spatial predictionsapply()
+ sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2) Plot the CV on the estimates: - + ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745 Each column corresponds to the order of the b priors: @@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -62.846 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame.
sim <- predict(m3, newdata = grid_yrs, nsim = 100) sim_last <- sim[grid_yrs$year == max(grid_yrs$year), ] # just plot last year pred_last <- predictions[predictions$year == max(grid_yrs$year), ] @@ -530,7 +534,7 @@ Spatial predictionspred_last$sd <- round(apply(exp(sim_last), 1, function(x) sd(x)), 2) pred_last$cv <- round(apply(exp(sim_last), 1, function(x) sd(x) / mean(x)), 2)
Plot the CV on the estimates:
+ ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c() @@ -543,7 +547,7 @@ Conditional effects + nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745
ggplot(pred_last, aes(X, Y, fill = cv)) + geom_raster() + scale_fill_viridis_c()
+ nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame. Adding priors @@ -176,7 +198,14 @@ Adding priors -head(fit$tmb_data$X_ij[[1]]) +head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745
nd <- data.frame( depth = seq(min(pcod$depth), max(pcod$depth), @@ -567,13 +571,13 @@ Conditional effects + visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame.
+ visreg::visreg(m3, "depth") Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix. - + ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame.
visreg::visreg(m3, "depth")
Or the ggeffects package for a marginal effects plot. This will also be faster since it relies on the already estimated coefficients and variance-covariance matrix.
+ ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`). @@ -587,7 +591,7 @@ Time-varying effects + m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame.
ggeffects::ggeffect(m3, "depth [0:500 by=1]") %>% plot() #> Warning: Removed 1 row containing missing values or values outside the scale range #> (`geom_line()`).
+ m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix. + +m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().** + +AIC(m4) #> [1] 12534.29 To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid(). - + nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() + Fitting the model with marginal likelihood @@ -161,7 +167,23 @@ Fitting the model with margi family = gaussian(), spatial = "on" ) -fit +fit +#> Spatial model fit by ML ['sdmTMB'] +#> Formula: observed ~ a1 +#> Mesh: mesh (isotropic covariance) +#> Data: sim_dat +#> Family: gaussian(link = 'identity') +#> +#> coef.est coef.se +#> (Intercept) 0.8 0.06 +#> a1 -0.4 0.01 +#> +#> Dispersion parameter: 0.20 +#> Matérn range: 0.32 +#> Spatial SD: 0.17 +#> ML criterion at convergence: -65.939 +#> +#> See ?tidy.sdmTMB to extract these values as a data frame.
m4 <- sdmTMB( density ~ 0 + as.factor(year), data = pcod, @@ -603,8 +607,9 @@ Time-varying effects#> `extra_time = c(2006, 2008, 2010, 2012, 2014, 2016)` #> Warning in sqrt(diag(cov)): NaNs produced #> Warning: The model may not have converged: non-positive-definite Hessian -#> matrix. -m4 +#> matrix.
+m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().**
m4 #> Spatiotemporal model fit by ML ['sdmTMB'] #> Formula: density ~ 0 + as.factor(year) #> Mesh: mesh (isotropic covariance) @@ -687,13 +692,14 @@ Time-varying effects#> #> See ?tidy.sdmTMB to extract these values as a data frame. #> -#> **Possible issues detected! Check output of sanity().** -AIC(m4) +#> **Possible issues detected! Check output of sanity().**
+AIC(m4) #> [1] 12534.29
AIC(m4) #> [1] 12534.29
To plot these, we make a data frame that contains all combinations of the time-varying covariate and time. This is easily created using expand.grid() or tidyr::expand_grid().
expand.grid()
tidyr::expand_grid()
+ nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced + + ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect. Visualize our simulated data: ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c() +
nd <- expand.grid( depth_scaled = seq(min(pcod$depth_scaled) + 0.2, max(pcod$depth_scaled) - 0.2, @@ -704,8 +710,9 @@ Time-varying effectsnd$depth_scaled2 <- nd$depth_scaled^2 p <- predict(m4, newdata = nd, se_fit = TRUE, re_form = NA) -#> Warning in sqrt(diag(cov)): NaNs produced - +#> Warning in sqrt(diag(cov)): NaNs produced
+ ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect.
ggplot(p, aes(depth_scaled, exp(est), ymin = exp(est - 1.96 * est_se), ymax = exp(est + 1.96 * est_se), diff --git a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png index ae4a1e6f7..74d8cb85b 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png and b/articles/web_only/basic-intro_files/figure-html/plot-cv-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png index 03dd2d70a..a03213e71 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png and b/articles/web_only/basic-intro_files/figure-html/residuals-mcmc-1.png differ diff --git a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png index 956fb3b8f..88ce6f332 100644 Binary files a/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png and b/articles/web_only/basic-intro_files/figure-html/tv-depth-eff-1.png differ diff --git a/articles/web_only/bayesian.html b/articles/web_only/bayesian.html index 83b332a45..2fb546cee 100644 --- a/articles/web_only/bayesian.html +++ b/articles/web_only/bayesian.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Bayesian estimation with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/bayesian.Rmd bayesian.Rmd @@ -142,12 +142,18 @@ Simulating data sigma_O = 0.2, seed = 123, B = c(0.8, -0.4) # B0 = intercept, B1 = a1 slope -) +) +#> The `seed` argument may be deprecated in the future. +#> We recommend instead setting the seed manually with `set.seed()` prior to +#> calling `sdmTMB_simulate()`. +#> We have encountered some situations where setting the seed via this argument +#> does not have the intended effect.
vignettes/web_only/bayesian.Rmd
bayesian.Rmd
Visualize our simulated data:
ggplot(sim_dat, aes(X, Y, colour = observed)) + geom_point() + scale_color_viridis_c()
-head(fit$tmb_data$X_ij[[1]])
head(fit$tmb_data$X_ij[[1]])
head(fit$tmb_data$X_ij[[1]]) +#> (Intercept) a1 +#> 1 1 -0.60189285 +#> 2 1 -0.99369859 +#> 3 1 1.02678506 +#> 4 1 0.75106130 +#> 5 1 -1.50916654 +#> 6 1 -0.09514745
Each column corresponds to the order of the b priors:
b
@@ -191,7 +220,23 @@ Adding priors= normal(location = c(0, 0), scale = c(5, 2)), ) ) -fit
-fit_stan
fit_stan
fit_stan +#> Inference for Stan model: sdmTMB. +#> 2 chains, each with iter=1000; warmup=500; thin=1; +#> post-warmup draws per chain=500, total post-warmup draws=1000. +#> +#> mean se_mean sd 2.5% 25% 50% 75% 97.5% n_eff Rhat +#> b_j[1] 0.80 0.01 0.06 0.68 0.76 0.80 0.84 0.92 82 1.02 +#> b_j[2] -0.40 0.00 0.01 -0.41 -0.40 -0.40 -0.39 -0.38 1747 1.00 +#> ln_tau_O -1.67 0.01 0.14 -1.93 -1.76 -1.67 -1.57 -1.42 169 1.01 +#> ln_phi -1.63 0.00 0.03 -1.69 -1.65 -1.63 -1.61 -1.56 1360 1.00 +#> omega_s[1] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.09 162 1.01 +#> omega_s[2] -0.06 0.01 0.09 -0.25 -0.12 -0.06 0.01 0.11 196 1.01 +#> omega_s[3] 0.01 0.01 0.10 -0.17 -0.05 0.01 0.08 0.20 196 1.01 +#> omega_s[4] -0.21 0.01 0.09 -0.40 -0.27 -0.21 -0.15 -0.02 206 1.01 +#> omega_s[5] -0.34 0.01 0.10 -0.53 -0.41 -0.33 -0.27 -0.13 247 1.00 +#> omega_s[6] -0.08 0.01 0.10 -0.27 -0.15 -0.08 0.00 0.13 268 1.01 +#> omega_s[7] -0.02 0.01 0.09 -0.19 -0.08 -0.02 0.04 0.15 172 1.01 +#> omega_s[8] -0.23 0.01 0.10 -0.42 -0.29 -0.23 -0.16 -0.05 219 1.01 +#> omega_s[9] -0.32 0.01 0.09 -0.50 -0.38 -0.32 -0.26 -0.13 166 1.02 +#> omega_s[10] 0.29 0.01 0.09 0.11 0.23 0.28 0.35 0.46 147 1.01 +#> omega_s[11] -0.16 0.01 0.09 -0.34 -0.22 -0.16 -0.09 0.02 208 1.01 +#> omega_s[12] 0.00 0.01 0.10 -0.19 -0.07 0.01 0.07 0.21 205 1.01 +#> omega_s[13] 0.19 0.01 0.09 0.02 0.13 0.19 0.25 0.36 166 1.01 +#> omega_s[14] -0.09 0.01 0.10 -0.29 -0.16 -0.09 -0.03 0.11 253 1.01 +#> omega_s[15] 0.22 0.01 0.09 0.06 0.16 0.22 0.28 0.40 152 1.01 +#> omega_s[16] -0.02 0.01 0.10 -0.21 -0.08 -0.02 0.05 0.16 198 1.01 +#> omega_s[17] -0.15 0.01 0.09 -0.34 -0.21 -0.14 -0.08 0.04 198 1.01 +#> omega_s[18] -0.28 0.01 0.11 -0.50 -0.35 -0.28 -0.21 -0.09 270 1.00 +#> omega_s[19] 0.00 0.01 0.10 -0.18 -0.06 0.00 0.07 0.19 211 1.01 +#> omega_s[20] 0.03 0.01 0.08 -0.14 -0.03 0.02 0.09 0.19 180 1.01 +#> omega_s[21] 0.08 0.01 0.10 -0.11 0.02 0.08 0.15 0.26 191 1.01 +#> omega_s[22] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.05 0.17 212 1.00 +#> omega_s[23] 0.12 0.01 0.09 -0.04 0.06 0.12 0.18 0.30 156 1.01 +#> omega_s[24] 0.20 0.01 0.10 0.00 0.13 0.19 0.26 0.40 256 1.00 +#> omega_s[25] 0.08 0.01 0.09 -0.09 0.01 0.07 0.14 0.26 167 1.01 +#> omega_s[26] -0.01 0.01 0.10 -0.21 -0.08 -0.01 0.06 0.20 215 1.01 +#> omega_s[27] -0.11 0.01 0.09 -0.30 -0.18 -0.10 -0.05 0.06 216 1.00 +#> omega_s[28] 0.12 0.01 0.10 -0.08 0.05 0.11 0.18 0.32 229 1.00 +#> omega_s[29] 0.30 0.01 0.09 0.12 0.23 0.30 0.36 0.47 212 1.01 +#> omega_s[30] -0.04 0.01 0.09 -0.22 -0.10 -0.04 0.02 0.14 193 1.01 +#> omega_s[31] 0.09 0.01 0.09 -0.08 0.04 0.09 0.15 0.26 154 1.01 +#> omega_s[32] 0.05 0.01 0.12 -0.18 -0.02 0.05 0.14 0.28 244 1.01 +#> omega_s[33] 0.08 0.01 0.09 -0.10 0.01 0.07 0.14 0.26 219 1.01 +#> omega_s[34] 0.04 0.01 0.09 -0.13 -0.03 0.04 0.10 0.20 163 1.01 +#> omega_s[35] 0.07 0.01 0.10 -0.10 0.00 0.07 0.14 0.26 230 1.01 +#> omega_s[36] 0.14 0.01 0.09 -0.04 0.08 0.15 0.20 0.33 197 1.01 +#> omega_s[37] 0.16 0.01 0.11 -0.06 0.09 0.16 0.24 0.39 275 1.00 +#> omega_s[38] 0.12 0.01 0.10 -0.09 0.05 0.11 0.19 0.31 217 1.01 +#> omega_s[39] -0.22 0.01 0.10 -0.40 -0.28 -0.22 -0.15 -0.04 186 1.01 +#> omega_s[40] -0.03 0.01 0.09 -0.21 -0.09 -0.03 0.03 0.17 197 1.01 +#> omega_s[41] 0.19 0.01 0.08 0.03 0.13 0.19 0.24 0.35 143 1.01 +#> omega_s[42] 0.21 0.01 0.09 0.01 0.14 0.21 0.27 0.38 173 1.01 +#> omega_s[43] 0.15 0.01 0.10 -0.04 0.07 0.15 0.21 0.35 221 1.01 +#> omega_s[44] 0.14 0.01 0.10 -0.07 0.08 0.14 0.20 0.32 194 1.01 +#> omega_s[45] 0.10 0.01 0.10 -0.10 0.02 0.10 0.16 0.30 248 1.00 +#> omega_s[46] 0.06 0.01 0.10 -0.14 -0.01 0.06 0.12 0.26 187 1.01 +#> omega_s[47] 0.31 0.01 0.09 0.13 0.24 0.31 0.37 0.50 166 1.01 +#> omega_s[48] -0.24 0.01 0.10 -0.44 -0.31 -0.24 -0.18 -0.06 196 1.01 +#> omega_s[49] 0.10 0.01 0.10 -0.10 0.04 0.10 0.17 0.32 255 1.01 +#> omega_s[50] -0.09 0.01 0.09 -0.26 -0.14 -0.09 -0.03 0.08 178 1.01 +#> omega_s[51] 0.25 0.01 0.11 0.04 0.17 0.25 0.32 0.48 247 1.01 +#> omega_s[52] -0.21 0.01 0.11 -0.43 -0.29 -0.21 -0.14 -0.01 251 1.01 +#> omega_s[53] 0.04 0.01 0.10 -0.16 -0.03 0.04 0.10 0.22 213 1.00 +#> omega_s[54] 0.03 0.01 0.10 -0.16 -0.04 0.03 0.10 0.22 214 1.00 +#> omega_s[55] -0.09 0.01 0.11 -0.30 -0.16 -0.09 -0.01 0.15 311 1.01 +#> omega_s[56] -0.42 0.01 0.10 -0.62 -0.48 -0.41 -0.35 -0.24 229 1.01 +#> omega_s[57] 0.01 0.01 0.11 -0.20 -0.07 0.00 0.08 0.24 274 1.00 +#> omega_s[58] -0.21 0.01 0.10 -0.40 -0.28 -0.21 -0.14 -0.01 201 1.01 +#> omega_s[59] 0.04 0.01 0.24 -0.43 -0.11 0.05 0.20 0.47 992 1.00 +#> omega_s[60] -0.23 0.01 0.28 -0.78 -0.41 -0.23 -0.03 0.27 1087 1.00 +#> omega_s[61] -0.26 0.01 0.24 -0.69 -0.41 -0.27 -0.10 0.20 662 1.00 +#> omega_s[62] -0.26 0.01 0.23 -0.72 -0.42 -0.26 -0.11 0.19 693 1.00 +#> omega_s[63] -0.27 0.01 0.23 -0.72 -0.43 -0.28 -0.12 0.18 807 1.00 +#> omega_s[64] 0.08 0.01 0.22 -0.37 -0.07 0.09 0.23 0.51 866 1.00 +#> omega_s[65] 0.17 0.01 0.22 -0.29 0.02 0.17 0.32 0.57 693 1.00 +#> omega_s[66] 0.16 0.01 0.22 -0.27 0.02 0.16 0.31 0.60 525 1.00 +#> omega_s[67] -0.03 0.01 0.22 -0.49 -0.17 -0.01 0.13 0.42 1154 1.00 +#> omega_s[68] -0.02 0.01 0.25 -0.50 -0.17 -0.02 0.15 0.47 1152 1.00 +#> omega_s[69] 0.00 0.01 0.22 -0.46 -0.15 0.00 0.15 0.43 458 1.01 +#> omega_s[70] 0.01 0.01 0.21 -0.42 -0.13 0.01 0.15 0.40 431 1.00 +#> omega_s[71] 0.17 0.01 0.24 -0.30 0.01 0.17 0.33 0.67 884 1.00 +#> omega_s[72] -0.12 0.01 0.24 -0.62 -0.29 -0.11 0.04 0.35 1064 1.00 +#> omega_s[73] -0.14 0.01 0.22 -0.58 -0.28 -0.15 0.01 0.30 600 1.00 +#> omega_s[74] -0.14 0.01 0.22 -0.56 -0.29 -0.14 0.01 0.26 624 1.00 +#> omega_s[75] -0.38 0.01 0.20 -0.76 -0.51 -0.38 -0.25 0.00 654 1.00 +#> omega_s[76] 0.10 0.01 0.21 -0.31 -0.04 0.10 0.23 0.51 848 1.00 +#> omega_s[77] 0.09 0.01 0.22 -0.34 -0.04 0.09 0.23 0.52 1053 1.00 +#> omega_s[78] -0.06 0.01 0.21 -0.47 -0.19 -0.06 0.08 0.35 1693 1.00 +#> omega_s[79] 0.06 0.01 0.16 -0.27 -0.05 0.06 0.17 0.37 622 1.00 +#> omega_s[80] -0.18 0.01 0.23 -0.65 -0.33 -0.18 -0.02 0.27 632 1.00 +#> omega_s[81] -0.07 0.01 0.20 -0.45 -0.20 -0.07 0.05 0.33 1165 1.00 +#> omega_s[82] -0.14 0.01 0.21 -0.55 -0.28 -0.14 0.00 0.29 810 1.00 +#> omega_s[83] -0.02 0.01 0.23 -0.47 -0.18 -0.01 0.14 0.44 1096 1.00 +#> omega_s[84] 0.12 0.01 0.20 -0.26 -0.02 0.12 0.25 0.53 952 1.00 +#> omega_s[85] -0.29 0.01 0.21 -0.68 -0.44 -0.30 -0.15 0.11 1111 1.00 +#> lp__ 136.06 0.93 9.26 118.11 129.87 136.38 142.46 152.88 100 1.02 +#> +#> Samples were drawn using NUTS(diag_e) at Fri May 17 19:00:37 2024. +#> For each parameter, n_eff is a crude measure of effective sample size, +#> and Rhat is the potential scale reduction factor on split chains (at +#> convergence, Rhat=1).
The Rhat values look reasonable (< 1.05). The n_eff (number of effective samples) values mostly look reasonable (> 100) for inference about the mean for all parameters @@ -267,10 +415,18 @@
Rhat
n_eff
plot(fit_stan) -pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") +#> 'pars' not specified. Showing first 10 parameters by default. +#> ci_level: 0.8 (80% intervals) +#> outer_level: 0.95 (95% intervals)
+pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot)
pars_plot <- c("b_j[1]", "b_j[2]", "ln_tau_O", "omega_s[1]") -bayesplot::mcmc_trace(fit_stan, pars = pars_plot) -bayesplot::mcmc_pairs(fit_stan, pars = pars_plot)
+bayesplot::mcmc_pairs(fit_stan, pars = pars_plot)
bayesplot::mcmc_pairs(fit_stan, pars = pars_plot)
nsim
+ set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay ) -See ?bayesplot::pp_check. The solid line represents the + +See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data. @@ -302,26 +459,30 @@ Plotting predictionspredict.sdmTMB(). - + pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
set.seed(19292) samps <- sdmTMBextra::extract_mcmc(fit_stan) s <- simulate(fit_mle, mcmc_samples = samps, nsim = 50) -bayesplot::pp_check( +bayesplot::pp_check( sim_dat$observed, yrep = t(s), - fun = bayesplot::ppc_dens_overlay + fun = bayesplot::ppc_dens_overlay )
See ?bayesplot::pp_check. The solid line represents the +
?bayesplot::pp_check
See ?bayesplot::pp_check. The solid line represents the density of the observed data and the light blue lines represent the density of 50 posterior predictive simulations. In this case, the simulated data seem consistent with the observed data.
+ pred <- predict(fit_mle, mcmc_samples = samps) The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample. - -dim(pred) + +dim(pred) +#> [1] 500 1000 We can summarize these draws in various ways to visualize them: - + sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
pred <- predict(fit_mle, mcmc_samples = samps)
The output is a matrix where each row corresponds to a row of predicted data and each column corresponds to a sample.
-dim(pred)
dim(pred)
+dim(pred) +#> [1] 500 1000
dim(pred) +#> [1] 500 1000
We can summarize these draws in various ways to visualize them:
+ sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c() + + + ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c() + Or predict on a grid for a given value of a1: - + nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + Extracting parameter posterior samples We can extract posterior samples with rstan::extract(), - + post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
sim_dat$post_mean <- apply(pred, 1, mean) sim_dat$post_sd <- apply(pred, 1, sd) ggplot(sim_dat, aes(X, Y, colour = post_mean)) + geom_point() + - scale_color_viridis_c() - + scale_color_viridis_c()
+ ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c()
ggplot(sim_dat, aes(X, Y, colour = post_sd)) + geom_point() + scale_color_viridis_c()
Or predict on a grid for a given value of a1:
a1
+ nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() +
nd <- expand.grid( X = seq(0, 1, length.out = 70), Y = seq(0, 1, length.out = 70), @@ -335,32 +496,39 @@ Plotting predictionsggplot(nd, aes(X, Y, fill = post_mean)) + geom_raster() + scale_fill_viridis_c() + - coord_fixed() - + coord_fixed()
+ ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed()
ggplot(nd, aes(X, Y, fill = post_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed()
We can extract posterior samples with rstan::extract(),
rstan::extract()
+ post <- rstan::extract(fit_stan) The result is a list where each element corresponds to a parameter or set of parameters: - + names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__" + +hist(post$b_j[, 1]) + As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation: - + ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) + Extracting the posterior of other predicted elements @@ -374,7 +542,7 @@ Extracting the pos For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB(). - + fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463 @@ -227,7 +229,7 @@ Single splits + clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
post <- rstan::extract(fit_stan)
The result is a list where each element corresponds to a parameter or set of parameters:
+ names(post) -hist(post$b_j[, 1]) +#> [1] "b_j" "ln_tau_O" "ln_phi" "omega_s" "lp__"
names(post) -hist(post$b_j[, 1])
+hist(post$b_j[, 1])
hist(post$b_j[, 1])
As an example of calculating a derived parameter, here we will calculate the marginal spatial random field standard deviation:
+ ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O) +
ln_kappa <- get_pars(fit_mle)$ln_kappa[1] # 2 elements since 2nd would be for spatiotemporal ln_tau_O <- post$ln_tau_O sigma_O <- 1 / sqrt(4 * pi * exp(2 * ln_tau_O + 2 * ln_kappa)) hist(sigma_O)
For example, let’s extract the spatial random field values "omega_s". Other options are documented in ?predict.sdmTMB().
"omega_s"
?predict.sdmTMB()
+ fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed() + + + ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed() + diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png index 6a70e95c0..2c520c26e 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-8-3.png differ diff --git a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png index cbf13c007..3e3bd4c2b 100644 Binary files a/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png and b/articles/web_only/bayesian_files/figure-html/unnamed-chunk-9-1.png differ diff --git a/articles/web_only/cross-validation.html b/articles/web_only/cross-validation.html index 1a11bf37c..70a4b4626 100644 --- a/articles/web_only/cross-validation.html +++ b/articles/web_only/cross-validation.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Cross-validation for model evaluation and comparison - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/cross-validation.Rmd cross-validation.Rmd @@ -210,11 +210,13 @@ Measuring model performance= 4 ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257 + +m_cv$sum_loglik # total log-likelihood #> [1] -6640.463
fit_pred <- predict( fit_mle, newdata = nd, @@ -388,12 +556,15 @@ Extracting the pos ggplot(nd, aes(X, Y, fill = spatial_rf_mean)) + geom_raster() + scale_fill_gradient2() + - coord_fixed() - + coord_fixed()
+ ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed()
ggplot(nd, aes(X, Y, fill = spatial_rf_sd)) + geom_raster() + scale_fill_viridis_c() + coord_fixed()
vignettes/web_only/cross-validation.Rmd
cross-validation.Rmd
+ m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257
m_cv$fold_loglik # fold log-likelihood -#> [1] -1720.122 -1756.138 -1502.946 -1661.257 -m_cv$sum_loglik # total log-likelihood +#> [1] -1720.122 -1756.138 -1502.946 -1661.257
+m_cv$sum_loglik # total log-likelihood #> [1] -6640.463
m_cv$sum_loglik # total log-likelihood #> [1] -6640.463
+ clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits) We can ignore the total log-likelihood, and just focus on the first element of list list: - + m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481 @@ -300,7 +305,7 @@ Model ensemblingmodel_list), where each list element is the output of a call to sdmTMB_cv(): - + weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
clust <- sample(1:2, size = nrow(pcod), replace = TRUE, prob = c(0.1, 0.9)) m_cv <- sdmTMB_cv( @@ -240,7 +242,7 @@ Single splits)
We can ignore the total log-likelihood, and just focus on the first element of list list:
+ m_cv$fold_loglik[[1]] #> [1] -608.5838 @@ -259,7 +261,7 @@ Comparing two or more models + clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481
m_cv$fold_loglik[[1]] #> [1] -608.5838
+ clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing. + + # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929 + +m2$sum_loglik #> [1] -6596.481
clust <- sample(seq_len(10), size = nrow(pcod), replace = TRUE) m1 <- sdmTMB_cv( @@ -270,8 +272,9 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing.
+ m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing.
m2 <- sdmTMB_cv( density ~ 0 + fyear + s(depth_scaled), data = pcod, @@ -280,12 +283,14 @@ Comparing two or more models= tweedie(link = "log") ) #> Running fits with `future.apply()`. -#> Set a parallel `future::plan()` to use parallel processing. - +#> Set a parallel `future::plan()` to use parallel processing.
+ # Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929
# Compare log-likelihoods -- higher is better! m1$sum_loglik -#> [1] -6748.929 -m2$sum_loglik +#> [1] -6748.929
+m2$sum_loglik #> [1] -6596.481
m2$sum_loglik #> [1] -6596.481
sdmTMB_cv()
+ weights <- sdmTMB_stacking(model_list) By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@ Model ensemblinginclude_folds argument. - + weights <- sdmTMB_stacking(model_list, include_folds = 1) diff --git a/articles/web_only/delta-models.html b/articles/web_only/delta-models.html index b1a6b8f2e..a866d6cfa 100644 --- a/articles/web_only/delta-models.html +++ b/articles/web_only/delta-models.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Philina English, Sean Anderson, Eric Ward, Lewis Barnett - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/delta-models.Rmd delta-models.Rmd @@ -302,25 +302,29 @@ Example with built-in delta model#> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614 + +tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14 + +tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120 + +tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
weights <- sdmTMB_stacking(model_list)
By default this calculation uses data from each fold. If instead, we had split the data into the 10/90 split (as in the example above), we @@ -308,7 +313,7 @@
+ weights <- sdmTMB_stacking(model_list, include_folds = 1)
weights <- sdmTMB_stacking(model_list, include_folds = 1)
vignettes/web_only/delta-models.Rmd
delta-models.Rmd
+tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614
tidy(fit_dg, model = 1) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) -0.343 0.614 -tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) -0.343 0.614
+tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14
tidy(fit_dg, model = 1, "ran_pars", conf.int = TRUE) #> # A tibble: 3 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> #> 1 range 61.4 14.1 39.2 96.3 #> 2 sigma_O 1.71 0.265 1.26 2.32 -#> 3 sigma_E 0.806 0.142 0.570 1.14 -tidy(fit_dg, model = 2) +#> 3 sigma_E 0.806 0.142 0.570 1.14
+tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120
tidy(fit_dg, model = 2) #> # A tibble: 1 × 3 #> term estimate std.error #> <chr> <dbl> <dbl> -#> 1 (Intercept) 3.67 0.120 -tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) +#> 1 (Intercept) 3.67 0.120
+tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB() for a description of values in the data frame. - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
tidy(fit_dg, model = 2, "ran_pars", conf.int = TRUE) #> # A tibble: 4 × 5 #> term estimate std.error conf.low conf.high #> <chr> <dbl> <dbl> <dbl> <dbl> @@ -334,7 +338,7 @@ Example with built-in delta modelpredict.sdmTMB()
+ grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) p <- predict(fit_dg, newdata = grid_yrs) str(p) @@ -367,7 +371,7 @@ Example with built-in delta modelIndex standardization with sdmTMB. - + p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
+ p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE) We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@ Example with built-in delta modelggeffects is not supported. See the vignette on using visreg with sdmTMB for more information. - + visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
p2 <- predict(fit_dg, newdata = grid_yrs, return_tmb_object = TRUE) ind_dg <- get_index(p2, bias_correct = FALSE)
We can plot conditional effects of covariates (such as depth in the @@ -377,12 +381,12 @@
+ visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component. - + visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
visreg_delta(fit_dg, xvar = "depth", model = 1, gg = TRUE) #> These are residuals for delta model component 1. Use the `model` argument to #> select the other component.
+ visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE) The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@ Example with built-in delta modelmodel=2 for only positive catches, or model=NA for combined predictions. See simulate.sdmTMB() for more details on simulation options. - + simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA) @@ -409,7 +413,7 @@ + glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
visreg_delta(fit_dg, xvar = "depth", model = 2, gg = TRUE)
The built-in delta models can also be evaluated with the residuals() functions in sdmTMB. Similarly to generating @@ -397,7 +401,7 @@
residuals()
model=NA
simulate.sdmTMB()
+ simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA)
simulations <- simulate(fit_dg, nsim = 5, seed = 5090, model = NA)
+ glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440… - + mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
glimpse(pcod) #> Rows: 2,143 #> Columns: 12 @@ -425,12 +429,12 @@ #> $ depth_sd <dbl> 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0.4448783, 0… #> $ depth_scaled <dbl> 0.3329252, 0.4526914, 0.5359529, 0.2877417, 0.8766077, 1… #> $ depth_scaled2 <dbl> 0.11083919, 0.20492947, 0.28724555, 0.08279527, 0.768440…
+ mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here: - + dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
mesh1 <- make_mesh(pcod, c("X", "Y"), cutoff = 20) # coarse for vignette speed
It is not necessary to use the same mesh for both models, but one can do so by updating the first mesh to match the reduced data frame as shown here:
+ dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
dat2 <- subset(pcod, density > 0) mesh2 <- make_mesh(dat2, xy_cols = c("X", "Y"), @@ -439,7 +443,7 @@ Intro to modelling with sdmTMB vignette, except that we will use s() for the depth effect. - + m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
s()
+ m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
m1 <- sdmTMB( formula = present ~ 0 + as.factor(year) + s(depth, k = 3), data = pcod, @@ -481,7 +485,7 @@ + m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
+ m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
m2 <- sdmTMB( formula = density ~ 0 + as.factor(year) + s(depth), data = dat2, @@ -517,8 +521,8 @@ #> #> Dispersion parameter: 0.94 #> Matérn range: 0.01 -#> Spatial SD: 727.49 -#> Spatiotemporal IID SD: 2065.49 +#> Spatial SD: 727.51 +#> Spatiotemporal IID SD: 2065.54 #> ML criterion at convergence: 5102.136 #> #> See ?tidy.sdmTMB to extract these values as a data frame. @@ -527,7 +531,7 @@ + pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
+ pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
pred <- grid_yrs # use the grid as template for saving our predictions p_bin <- predict(m1, newdata = grid_yrs) p_pos <- predict(m2, newdata = grid_yrs) @@ -543,7 +547,7 @@ + set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
+ set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
set.seed(28239) p_bin_sim <- predict(m1, newdata = grid_yrs, nsim = 100) p_pos_sim <- predict(m2, newdata = grid_yrs, nsim = 100) @@ -554,18 +558,18 @@ + pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
+ pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median) - + ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
pred$median <- apply(p_combined_sim, 1, median) plot(pred$est_exp, pred$median)
+ ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt") And we can calculate spatial uncertainty: - + pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
ggplot(subset(pred, year == 2017), aes(X, Y, fill = median)) + geom_raster() + coord_fixed() + scale_fill_viridis_c(trans = "sqrt")
And we can calculate spatial uncertainty:
+ pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
pred$cv <- apply(p_combined_sim, 1, function(x) sd(x) / mean(x)) ggplot(subset(pred, year == 2017), aes(X, Y, fill = cv)) + # 2017 as an example geom_raster() + diff --git a/articles/web_only/delta-models_files/figure-html/cv-1.png b/articles/web_only/delta-models_files/figure-html/cv-1.png index bdd921832..fea4e82b8 100644 Binary files a/articles/web_only/delta-models_files/figure-html/cv-1.png and b/articles/web_only/delta-models_files/figure-html/cv-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png index 16c806e32..87d0400fd 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-19-1.png differ diff --git a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png index 5ecc651ea..da418331e 100644 Binary files a/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png and b/articles/web_only/delta-models_files/figure-html/unnamed-chunk-20-1.png differ diff --git a/articles/web_only/ggeffects.html b/articles/web_only/ggeffects.html index a58161b52..ef2573aa7 100644 --- a/articles/web_only/ggeffects.html +++ b/articles/web_only/ggeffects.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -87,7 +87,7 @@ Julia Indivero, Sean Anderson, Lewis Barnett, Philina English, Eric Ward - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/ggeffects.Rmd ggeffects.Rmd diff --git a/articles/web_only/index-standardization.html b/articles/web_only/index-standardization.html index 790080f4a..b972be05c 100644 --- a/articles/web_only/index-standardization.html +++ b/articles/web_only/index-standardization.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -85,7 +85,7 @@ Index standardization with sdmTMB - 2024-05-08 + 2024-05-17 Source: vignettes/web_only/index-standardization.Rmd index-standardization.Rmd @@ -149,15 +149,16 @@ 2024-05-08 #> Note what used to be the default sdmTMB residuals (before version 0.4.3.9005) #> are now `type = 'mle-eb'`. We recommend using the current default `'mle-mvn'`, #> which takes one sample from the approximate posterior of the random effects or -#> `dharma_residuals()` using a similar approach. -# Also see residuals(..., type = "mle-mcmc") which are better but slower +#> `dharma_residuals()` using a similar approach. + +# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids) - + qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
vignettes/web_only/ggeffects.Rmd
ggeffects.Rmd
vignettes/web_only/index-standardization.Rmd
index-standardization.Rmd
+# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids)
# Also see residuals(..., type = "mle-mcmc") which are better but slower hist(pcod$resids)
+ qqnorm(pcod$resids) abline(a = 0, b = 1) - + ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
qqnorm(pcod$resids) abline(a = 0, b = 1)
+ ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed() @@ -165,7 +166,7 @@ 2024-05-08 domain. There is a grid built into the package for Queen Charlotte Sound named qcs_grid. Our prediction grid also needs to have all the covariates that we used in the model above. - + glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
ggplot(pcod, aes(X, Y, col = resids)) + scale_colour_gradient2() + geom_point() + facet_wrap(~year) + coord_fixed()
qcs_grid
+ glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01… We can replicate our grid across all years: - + grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
glimpse(qcs_grid) #> Rows: 7,314 #> Columns: 5 @@ -175,13 +176,13 @@ 2024-05-08 #> $ depth_scaled <dbl> 1.56081222, 0.56976988, 0.36336929, 0.12570465, 0.122036… #> $ depth_scaled2 <dbl> 2.436134794, 0.324637712, 0.132037240, 0.015801659, 0.01…
We can replicate our grid across all years:
+ grid_yrs <- replicate_df(qcs_grid, "year", unique(pcod$year)) Now make the predictions on new data. - + predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
Now make the predictions on new data.
+ predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE) Let’s make a small function to make maps. - + plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
predictions <- predict(m, newdata = grid_yrs, return_tmb_object = TRUE)
Let’s make a small function to make maps.
+ plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
plot_map <- function(dat, column) { ggplot(dat, aes(X, Y, fill = {{ column }})) + geom_raster() + @@ -191,13 +192,13 @@ 2024-05-08 There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects: - + plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
There are four kinds of predictions that we get out of the model. First we will show the predictions that incorporate all fixed effects and random effects:
+ plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)") We can also look at just the fixed effects, here year: - + plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
plot_map(predictions$data, exp(est)) + scale_fill_viridis_c(trans = "sqrt") + ggtitle("Prediction (fixed effects + all random effects)")
We can also look at just the fixed effects, here year:
+ plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt") @@ -207,7 +208,7 @@ 2024-05-08 effects. In other words, these deviations represent consistent biotic and abiotic factors that are affecting biomass density but are not accounted for in the model. - + plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
plot_map(predictions$data, exp(est_non_rf)) + ggtitle("Prediction (fixed effects only)") + scale_fill_viridis_c(trans = "sqrt")
+ plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2() @@ -217,7 +218,7 @@ 2024-05-08 random effect deviations. These represent biotic and abiotic factors that are changing through time and are not accounted for in the model. - + plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
plot_map(predictions$data, omega_s) + ggtitle("Spatial random effects only") + scale_fill_gradient2()
+ plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2() @@ -232,15 +233,15 @@ 2024-05-08 were not fully in the survey domain (or were on land), we could feed a vector of grid areas to the area argument that matched the number of grid cells. - -index <- get_index(predictions, area = 4, bias_correct = TRUE) +index <- get_index(predictions, area = 4, bias_correct = TRUE) + ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)') These are our biomass estimates: - + mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
plot_map(predictions$data, epsilon_st) + ggtitle("Spatiotemporal random effects only") + scale_fill_gradient2()
area
-index <- get_index(predictions, area = 4, bias_correct = TRUE)
index <- get_index(predictions, area = 4, bias_correct = TRUE)
+index <- get_index(predictions, area = 4, bias_correct = TRUE)
ggplot(index, aes(year, est)) + geom_line() + geom_ribbon(aes(ymin = lwr, ymax = upr), alpha = 0.4) + xlab('Year') + ylab('Biomass estimate (kg)')
These are our biomass estimates:
+ mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2)) @@ -323,7 +324,7 @@ 2024-05-08 prediction grid. For more complicated spatial polygons you could intersect the polygon on the prediction grid using something like sf::st_intersects(). - + qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
mutate(index, cv = sqrt(exp(se^2) - 1)) %>% select(-log_est, -se) %>% knitr::kable(format = "pandoc", digits = c(0, 0, 0, 0, 2))
sf::st_intersects()
+ qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366 We can visually compare the two indexes: - + mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
qcs_grid_south <- grid_yrs[grid_yrs$Y < 5700, ] predictions_south <- predict(m, newdata = qcs_grid_south, return_tmb_object = TRUE) @@ -337,7 +338,7 @@ 2024-05-08 #> 5 2009 316483.8 203600.9 491952.5 12.66503 0.2250603 #> 6 2011 432243.6 292909.8 637857.0 12.97674 0.1985366
We can visually compare the two indexes:
+ mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large + +summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range. and make the same plot: - + p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
mutate(index, region = "all") %>% bind_rows(mutate(index_south, region = "south")) %>% ggplot(aes(year, est)) + diff --git a/articles/web_only/poisson-link.html b/articles/web_only/poisson-link.html index df738d351..48bf13100 100644 --- a/articles/web_only/poisson-link.html +++ b/articles/web_only/poisson-link.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -266,8 +266,9 @@ A Poisson-link-delta-gamma alter #> ✔ No standard errors look unreasonably large #> ✔ No sigma parameters are < 0.01 #> ✔ No sigma parameters are > 100 -#> ✔ Range parameters don't look unreasonably large -summary(fit_dpg) +#> ✔ Range parameters don't look unreasonably large
+summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range.
summary(fit_dpg) #> Spatial model fit by REML ['sdmTMB'] #> Formula: catch_weight ~ 0 + as.factor(year) + s(log_depth) #> Mesh: mesh (anisotropic covariance) @@ -275,7 +276,7 @@ A Poisson-link-delta-gamma alter #> Family: delta_gamma(link1 = 'log', link2 = 'log', type = 'poisson-link') #> #> Delta/hurdle model 1: ----------------------------------- -#> Family: binomial(link = 'logit') +#> Family: binomial(link = 'log') #> coef.est coef.se #> as.factor(year)2004 1.96 0.77 #> as.factor(year)2006 2.89 0.77 @@ -324,7 +325,7 @@ A Poisson-link-delta-gamma alter #> See ?tidy.sdmTMB to extract these values as a data frame. #> See ?plot_anisotropy to plot the anisotropic range.
and make the same plot:
+ p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
p_dpg <- predict(fit_dpg, newdata = nd, re_form = NA) ggplot(p_dpg, aes(log_depth, est1 + est2, colour = year)) + geom_line() + @@ -340,7 +341,7 @@ Examining the model We’ll make some predictions across depths but for a single year for simplicity: - + nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
We’ll make some predictions across depths but for a single year for simplicity:
+ nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA) @@ -348,7 +349,7 @@ Examining the model catch into group numbers density, weight per group, encounter probability, and positive rate: - + n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
nd2010 <- dplyr::filter(nd, year == 2010) p_pdg <- predict(fit_dpg, newdata = nd2010, re_form = NA)
+ n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
n <- exp(p_pdg$est1) w <- exp(p_pdg$est2) p <- 1 - exp(-n) @@ -361,13 +362,13 @@ Examining the model p * r: encounter probability \(\times\) positive catch rate. These give identical answers: - + lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
p * r: encounter probability \(\times\) positive catch rate.
p * r
These give identical answers:
+ lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1) Let’s plot out all the components and possible combinations: - + g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
lims <- c(0, max(p * r)) plot(n * w, p * r, xlim = lims, ylim = lims) abline(0, 1)
Let’s plot out all the components and possible combinations:
+ g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame. + + fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
g1 <- ggplot(p_pdg, aes(log_depth, n)) + geom_line() + ggtitle("Expected group density") @@ -446,12 +447,13 @@ + p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame.
+ p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395 + +1 - exp(-exp(log_n)) #> [1] 0.78 You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame.
p <- 0.78 log_n <- log(-log(1 - p)) log_n -#> [1] 0.4148395 -1 - exp(-exp(log_n)) +#> [1] 0.4148395
+1 - exp(-exp(log_n)) #> [1] 0.78
1 - exp(-exp(log_n)) #> [1] 0.78
You can see how the cloglog inverse link is the same as the first part of our Poisson-link delta model. However, the diff --git a/articles/web_only/pretty-plots.html b/articles/web_only/pretty-plots.html index 851b2b7ae..24eed8614 100644 --- a/articles/web_only/pretty-plots.html +++ b/articles/web_only/pretty-plots.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 diff --git a/articles/web_only/residual-checking.html b/articles/web_only/residual-checking.html index 7c476db58..1f379a7c2 100644 --- a/articles/web_only/residual-checking.html +++ b/articles/web_only/residual-checking.html @@ -26,7 +26,7 @@ sdmTMB - 0.5.0.9006 + 0.5.0.9007 @@ -149,8 +149,9 @@ Residual checking with worked ex #> Spatial SD: 1.20 #> ML criterion at convergence: 2887.957 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame.
+ fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame.
fit_nb2 <- update(fit_pois, family = nbinom2()) fit_nb2 #> Spatial model fit by ML ['sdmTMB'] @@ -168,8 +169,9 @@ Residual checking with worked ex #> Spatial SD: 0.42 #> ML criterion at convergence: 1735.452 #> -#> See ?tidy.sdmTMB to extract these values as a data frame. - +#> See ?tidy.sdmTMB to extract these values as a data frame.
+ fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
fit_nb2_miss <- update(fit_nb2, formula. = observed ~ 1) fit_nb2_miss #> Spatial model fit by ML ['sdmTMB'] @@ -198,13 +200,13 @@ Analytical randomized-quantile MLEs (Maximum Likelihood Estimates) and random effects taken from a single sample of their approximate distribution (more details below): - + set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
+ set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1) - + set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
set.seed(123) rq_res <- residuals(fit_pois, type = "mle-mvn") rq_res <- rq_res[is.finite(rq_res)] # some Inf qqnorm(rq_res);abline(0, 1)
+ set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
set.seed(123) rq_res <- residuals(fit_nb2, type = "mle-mvn") @@ -228,14 +230,14 @@ MCMC-based randomized-quantile relax that assumption, we can sample the random effects with MCMC with the fixed effects held at their MLEs. We do this with the sdmTMBextra::predict_mle_mcmc() function in the sdmTMBextra. - + set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
sdmTMBextra::predict_mle_mcmc()
+ set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1: + +mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1) @@ -266,37 +269,39 @@ Simulation-based randomi We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals: - + s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
set.seed(123) samps <- sdmTMBextra::predict_mle_mcmc(fit_nb2, mcmc_iter = 800, mcmc_warmup = 400) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.000966 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.66 seconds. +#> Chain 1: Gradient evaluation took 0.000962 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 9.62 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -252,11 +254,12 @@ MCMC-based randomized-quantile #> Chain 1: Iteration: 720 / 800 [ 90%] (Sampling) #> Chain 1: Iteration: 800 / 800 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 6.774 seconds (Warm-up) -#> Chain 1: 4.814 seconds (Sampling) -#> Chain 1: 11.588 seconds (Total) -#> Chain 1: -mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) +#> Chain 1: Elapsed Time: 6.724 seconds (Warm-up) +#> Chain 1: 4.763 seconds (Sampling) +#> Chain 1: 11.487 seconds (Total) +#> Chain 1:
+mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1)
mcmc_res <- residuals(fit_nb2, type = "mle-mcmc", mcmc_samples = samps) qqnorm(mcmc_res) abline(0, 1)
We can also take simulations from the fitted model to use with simulation-based randomized quantile residuals:
+ s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn") These return a matrix where each row represents a row of data and each column is a simulation draw: - + dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
s_pois <- simulate(fit_pois, nsim = 500, type = "mle-mvn") s_nb2_miss <- simulate(fit_nb2_miss, nsim = 500, type = "mle-mvn") s_nb2 <- simulate(fit_nb2, nsim = 500, type = "mle-mvn")
These return a matrix where each row represents a row of data and each column is a simulation draw:
+ dim(s_pois) #> [1] 1000 500 We can look at whether fitted models are consistent with the observed number of zeros: - + sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
dim(s_pois) #> [1] 1000 500
We can look at whether fitted models are consistent with the observed number of zeros:
+ sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527 + +sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788 + +sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644 There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable. Plot DHARMa residuals: - + dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
sum(dat$observed == 0) / length(dat$observed) -#> [1] 0.527 -sum(s_pois == 0)/length(s_pois) -#> [1] 0.292788 -sum(s_nb2 == 0)/length(s_nb2) +#> [1] 0.527
+sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788
sum(s_pois == 0)/length(s_pois) +#> [1] 0.292788
+sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644
sum(s_nb2 == 0)/length(s_nb2) #> [1] 0.524644
There are obviously too few zeros in the data simulated from the Poisson model but the NB2 model seems reasonable.
Plot DHARMa residuals:
+ dharma_residuals(s_pois, fit_pois) We could also return the DHARMa object, which lets us use other DHARMa tools: - + r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
dharma_residuals(s_pois, fit_pois)
We could also return the DHARMa object, which lets us use other DHARMa tools:
+ r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois) - + DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111 + +DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) #> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois) +#> alternative hypothesis: Distance-based autocorrelation + +DHARMa::testZeroInflation(r_pois) #> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
r_pois <- dharma_residuals(s_pois, fit_pois, return_DHARMa = TRUE) plot(r_pois)
+ DHARMa::testResiduals(r_pois) #> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y) +#> 0.111
DHARMa::testResiduals(r_pois)
#> $uniformity @@ -364,8 +369,9 @@ Simulation-based randomi #> 0.09219791 0.13212606 #> sample estimates: #> frequency of outliers (expected: 0.00399201596806387 ) -#> 0.111 -DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y)
+DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y)
DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y)
#> #> DHARMa Moran's I test for distance-based autocorrelation @@ -373,8 +379,9 @@ Simulation-based randomi #> data: r_pois #> observed = -0.0022978, expected = -0.0010010, sd = 0.0026264, p-value = #> 0.6215 -#> alternative hypothesis: Distance-based autocorrelation -DHARMa::testZeroInflation(r_pois)
+DHARMa::testZeroInflation(r_pois)
DHARMa::testZeroInflation(r_pois)
#> #> DHARMa zero-inflation test via comparison to expected zeros with @@ -390,11 +397,11 @@ Simulation-based randomi the mean (resulting in some large outlying value) for the Poisson distribution. Lets try with the correct model: - + r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
Lets try with the correct model:
+ r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2) - + DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
r_nb2 <- dharma_residuals(s_nb2, fit_nb2, return_DHARMa = TRUE) plot(r_nb2)
+ DHARMa::testZeroInflation(r_nb2) #> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided Everything looks fine. What about the model where we were missing a predictor? - + r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
DHARMa::testZeroInflation(r_nb2)
#> @@ -406,20 +413,20 @@ Simulation-based randomi #> alternative hypothesis: two.sided
Everything looks fine.
What about the model where we were missing a predictor?
+ r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss) The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor: - + DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
r_nb2_miss <- dharma_residuals(s_nb2_miss, fit_nb2_miss, return_DHARMa = TRUE) plot(r_nb2_miss)
The plot on the right represents simulated residuals against the prediction without the random effects, which here is just an intercept. Lets try plotting the residuals against the missing predictor:
+ DHARMa::plotResiduals(r_nb2_miss, form = dat$a1) We can see a trend in the residuals against ‘a1’ since we have missed including it in the model. We can also see the difference in the log likelihood or the AIC: - + AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
DHARMa::plotResiduals(r_nb2_miss, form = dat$a1)
We can see a trend in the residuals against ‘a1’ since we have missed including it in the model.
We can also see the difference in the log likelihood or the AIC:
+ AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
AIC(fit_nb2_miss, fit_nb2) #> df AIC #> fit_nb2_miss 4 3642.665 @@ -441,7 +448,7 @@ The need for one-sample residualsHere we will show why this is necessary. We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects. - + set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect. + +fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data: - + set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
We’ll start by simulating some data with Gaussian observation error and spatial and spatiotemporal random effects.
+ set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh) +#> does not have the intended effect.
set.seed(123) predictor_dat <- data.frame( X = runif(1000), Y = runif(1000), @@ -465,18 +472,19 @@ The need for one-sample residuals#> We recommend instead setting the seed manually with `set.seed()` prior to #> calling `sdmTMB_simulate()`. #> We have encountered some situations where setting the seed via this argument -#> does not have the intended effect. -fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh)
+fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh)
fit <- sdmTMB(observed ~ 1, data = sim_dat, time = "year", mesh = mesh)
If we use the empirical Bayes (EB) random effect values (the values of the random effects that maximize the log likelihood conditional on the estimated fixed effects), our residuals look off even though our model is perfectly matched to our simulated data:
+ set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1) - + ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
set.seed(1) r1 <- residuals(fit, type = "mle-eb") qqnorm(r1);abline(0, 1)
+ ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
ks.test(r1, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -488,12 +496,12 @@ The need for one-sample residuals\(\operatorname{N}(0, 1)\) If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals: - + set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
If instead we returned to our single sample from the assumed MVN random effect distribution, we get the ‘correct’ residuals:
+ set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1) - + ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
set.seed(1) r2 <- residuals(fit, type = "mle-mvn") qqnorm(r2);abline(0, 1)
+ ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
ks.test(r2, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -505,13 +513,13 @@ The need for one-sample residualsWe could also sample that observation of random effects using MCMC (with the fixed effects still held at their MLEs), which relaxes our assumptions, but is much more time intensive for large models. - + samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
+ samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess + +r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1) - + ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
samp <- sdmTMBextra::predict_mle_mcmc(fit, mcmc_iter = 400, mcmc_warmup = 200) #> #> SAMPLING FOR MODEL 'tmb_generic' NOW (CHAIN 1). #> Chain 1: -#> Chain 1: Gradient evaluation took 0.002354 seconds -#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 23.54 seconds. +#> Chain 1: Gradient evaluation took 0.002192 seconds +#> Chain 1: 1000 transitions using 10 leapfrog steps per transition would take 21.92 seconds. #> Chain 1: Adjust your expectations accordingly! #> Chain 1: #> Chain 1: @@ -528,9 +536,9 @@ The need for one-sample residuals#> Chain 1: Iteration: 360 / 400 [ 90%] (Sampling) #> Chain 1: Iteration: 400 / 400 [100%] (Sampling) #> Chain 1: -#> Chain 1: Elapsed Time: 31.07 seconds (Warm-up) -#> Chain 1: 30.552 seconds (Sampling) -#> Chain 1: 61.622 seconds (Total) +#> Chain 1: Elapsed Time: 31.226 seconds (Warm-up) +#> Chain 1: 30.704 seconds (Sampling) +#> Chain 1: 61.93 seconds (Total) #> Chain 1: #> Warning: The largest R-hat is 1.08, indicating chains have not mixed. #> Running the chains for more iterations may help. See @@ -540,11 +548,12 @@ The need for one-sample residuals#> https://mc-stan.org/misc/warnings.html#bulk-ess #> Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable. #> Running the chains for more iterations may help. See -#> https://mc-stan.org/misc/warnings.html#tail-ess -r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) +#> https://mc-stan.org/misc/warnings.html#tail-ess
+r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1)
r3 <- residuals(fit, type = "mle-mcmc", mcmc_samples = samp) qqnorm(r3);abline(0, 1)
+ ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
ks.test(r3, pnorm) #> #> Asymptotic one-sample Kolmogorov-Smirnov test @@ -557,7 +566,7 @@ The need for one-sample residuals. A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package. - + set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
A similar issue applies to simulation-based quantile residuals, as implemented in the DHARMa package.
+ set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
set.seed(1) simulate(fit, nsim = 500, type = "mle-eb") |> dharma_residuals(fit) @@ -567,7 +576,7 @@ The need for one-sample residuals Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution. - +
Instead we can use a draw from the random effects ‘posterior’ assuming an MVN distribution.
+