If the code in this vignette has not been evaluated, a rendered version is available on the documentation site under ‘Articles’.
library(sdmTMB)
We will start with some data simulated from scratch. We will simulate from an NB2 negative binomial observation model, a spatial random field, an intercept, and one predictor named ‘a1’ that will have a linear effect on the observed data.
set.seed(1)
<- data.frame(X = runif(1000), Y = runif(1000), a1 = rnorm(1000))
predictor_dat <- make_mesh(predictor_dat, xy_cols = c("X", "Y"), cutoff = 0.1)
mesh <- sdmTMB_simulate(
dat formula = ~ 1 + a1,
data = predictor_dat,
mesh = mesh,
family = nbinom2(link = "log"),
phi = 0.2,
range = 0.4,
sigma_O = 0.3,
seed = 1,
B = c(0.2, 0.5) # B0 = intercept, B1 = a1 slope
)
Next, we will fit versions with various responses and predictors. The first model will use the Poisson instead of the NB2. The 2nd model will match the simulated data. The third model is missing the ‘a1’ predictor. We’ll use a PC prior on the Matérn parameters to aid in estimation.
<- pc_matern(range_gt = 0.1, sigma_lt = 1)
pc
<- sdmTMB(observed ~ 1 + a1, data = dat, family = poisson(), mesh = mesh,
fit_pois priors = sdmTMBpriors(matern_s = pc))
fit_pois
<- sdmTMB(observed ~ 1 + a1, data = dat, family = nbinom2(), mesh = mesh,
fit_nb2 priors = sdmTMBpriors(matern_s = pc))
fit_nb2
<- sdmTMB(observed ~ 1, data = dat, family = nbinom2(), mesh = mesh,
fit_nb2_miss priors = sdmTMBpriors(matern_s = pc))
fit_nb2_miss
We can see just by looking at these fits that the Poisson model inflates the spatial random field standard deviation (SD) compared to the truth. The model missing the ‘a1’ predictor does so to a lesser degree.
Here are randomized quantile residuals at fixed effect MLEs (Maximum Likelihood Estimates) and random effects that maximize the log likelihood at estimated fixed effects:
<- residuals(fit_pois)
rq_res <- rq_res[is.finite(rq_res)] # some Inf
rq_res qqnorm(rq_res);qqline(rq_res)
<- residuals(fit_nb2)
rq_res qqnorm(rq_res);qqline(rq_res)
These use the approach from Dunn and Smyth (1996). They are also
known as PIT (probability-integral-transform) residuals. They apply
randomization to integer response values, transform the residuals using
the distribution function (e.g., pnorm()
), simulate from a
uniform distribution, and transform the samples such that they would be
Gaussian if consistent with the model. You can see the source code at https://github.com/pbs-assess/sdmTMB/blob/master/R/residuals.R
We can see here that there are likely issues with the Poisson model in the tails.
These types of residuals are known to have statistical issues for state-space models; even if the model is the ‘correct’ model, the QQ plot may appear to have problems (Thygesen et al. 2017).
One-step-ahead residuals (Thygesen et al. 2017) are one option to fix this problem (although slow to calculate). Another option is to take a draw from the posterior with MCMC (e.g., Rufener et al. 2021). Also see https://kaskr.github.io/adcomp/_book/Validation.html
Here we will draw MCMC predictions and calculate residuals. The fixed effects will be fixed at their maximum likelihood estimates (MLE) and the random effects will be sampled. We will only take a single draw for speed:
<- residuals(fit_nb2, type = "mle-mcmc", mcmc_iter = 201, mcmc_warmup = 200)
mcmc_res qqnorm(mcmc_res);qqline(mcmc_res)
We can see these look a bit better. Remember, this is the ‘correct’ model.
We can take simulations from the fitted model to use with simulation-based residuals:
<- simulate(fit_pois, nsim = 500)
s_pois <- simulate(fit_nb2_miss, nsim = 500)
s_nb2_miss <- simulate(fit_nb2, nsim = 500) s_nb2
These return a matrix where each row represents a row of data and each column is a simulation draw:
dim(s_pois)
Test whether fitted models are consistent with the observed number of zeros:
sum(dat$observed == 0) / length(dat$observed)
sum(s_pois == 0)/length(s_pois)
sum(s_nb2 == 0)/length(s_nb2)
There are obviously too few zeros in the data simulated from the Poisson model.
Plot DHARMa residuals:
simulate(fit_pois, nsim = 300) %>%
dharma_residuals(fit_pois)
We could also do that manually, which lets us use other DHARMa tools:
# My reading of DHARMa documation is that the predicted response for the
# residuals vs. fitted plot should ideally not include the random effects:
<- fit_pois$family$linkinv(predict(fit_pois)$est_non_rf)
pred_fixed <- DHARMa::createDHARMa(
r_pois simulatedResponse = s_pois,
observedResponse = dat$observed,
fittedPredictedResponse = pred_fixed
)plot(r_pois)
::testResiduals(r_pois)
DHARMa::testSpatialAutocorrelation(r_pois, x = dat$X, y = dat$Y)
DHARMa::testZeroInflation(r_pois) DHARMa
In the QQ residual plots we clearly see evidence of over dispersion compared to the Poisson. Note the values clumping near 1.0 on the observed axis and deviating downwards towards 0.0 observed. This is indicative of too many zeros and especially too many large values compared to the assumed Poisson distribution.
Lets try with the correct model:
simulate(fit_nb2, nsim = 300) %>%
dharma_residuals(fit_nb2)
Everything looks fine. But, again, the MCMC-based residuals above are likely the best approach.
What about the model where we were missing a predictor?
<- fit_nb2_miss$family$linkinv(predict(fit_nb2_miss)$est_non_rf)
pred_fixed <- DHARMa::createDHARMa(
r_nb2_miss simulatedResponse = s_nb2_miss,
observedResponse = dat$observed,
fittedPredictedResponse = pred_fixed
)plot(r_nb2_miss)
This looks fine so far, but 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:
::plotResiduals(r_nb2_miss, form = dat$a1) DHARMa
We can see a slight 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 by using the
AIC()
method:
# negative log likelihood is lower;
# i.e. log likelihood is higher, but we do have one more parameter
$model$objective
fit_nb2$model$objective
fit_nb2_missAIC(fit_nb2_miss, fit_nb2) # AIC supports including the 'a1' predictor
The above used simulations with the parameters fixed at their Maximum Likelihood Estimate (MLE) and predictions conditional on the fitted random effects. Alternatively, we could simulate with the parameters drawn from their joint precision matrix to encapsulate uncertainty about the parameters. This may be a better test for residual analysis, but this is an open area of research as far as I can tell.
# simulate with the parameters drawn from the joint precision matrix:
<- simulate(fit_nb2, nsim = 1, params = "MVN") s2
Or we could simulate with new random fields based on the estimated parameters governing the random fields (range and SD):
# simulate with new random fields:
<- simulate(fit_nb2, nsim = 1, re_form = ~ 0) s3
We could, of course, combine those two options:
# simulate with new random fields and new parameter draws:
<- simulate(fit_nb2, nsim = 500, params = "MVN", re_form = ~ 0)
s4 <- fit_nb2$family$linkinv(predict(fit_nb2)$est_non_rf)
pred_fixed <- DHARMa::createDHARMa(
r_nb2 simulatedResponse = s4,
observedResponse = dat$observed,
fittedPredictedResponse = pred_fixed
)plot(r_nb2)
These also look OK.
For help interpreting the DHARMa residual plots, see
vignette("DHARMa", package="DHARMa")
.
Dunn, P.K., and Smyth, G.K. 1996. Randomized Quantile Residuals. Journal of Computational and Graphical Statistics 5(3): 236–244.
Rufener, M.-C., Kristensen, K., Nielsen, J.R., and Bastardie, F. 2021. Bridging the gap between commercial fisheries and survey data to model the spatiotemporal dynamics of marine species. Ecological Applications In press: e02453.
Thygesen, U.H., Albertsen, C.M., Berg, C.W., Kristensen, K., and Nielsen, A. 2017. Validation of ecological state space models using the Laplace approximation. Environ Ecol Stat 24(2): 317–339.