1 About

In this topic we show an example of the Barrier model, with simulation, inference and plotting. The example terrain/map/study-area has a barrier going from west to east, and a gap in the middle of the barrier.

This page is (as usual) a self-contained example of a good solution.

At the end follows sections on how to avoid errors when constructing an example like this, and some extra information on how to do things that are slightly different from this example.

1.1 Initialisation

We load the libraries and functions we need. You need to have a “testing version” of INLA.

You may need to install these libraries (Installation and general troubleshooting). We also set random seeds to be used later.

library(INLA)
library(fields)
library(rgdal)
library(viridisLite)

set.seed(2016)
set.inla.seed = 2016

2 Polygons and the Mesh

We will create the representation of our spatial domain, i.e. the locations where the random effect is defined.

We set up a square polygon p to define the barrier. In this example, I know I want to have a \([0, 10]\times [0, 10]\) study area, and I construct the barrier to fit inside this. In general, to use these barrier models, you need the barrier region (e.g. land) to be represented by a polygon. In most cases, you will use shapefiles or maps or other sources for determining your barrier, as in BTopic127 and BTopic128.

Talking about polygons in general, however, is out of the scope of this tutorial. More information about polygons can be found in the R-packages sp and maps.

smalldist = 0.2
# - the width of the opening in the barrier
width = 0.5
# - The width/thickness of the barrier

local.square.polygon = function(xlim, ylim){
# - output is a square
  xlim = range(xlim); ylim = range(ylim)
  corner1 = c(xlim[1], ylim[2])
  corner2 = c(xlim[2], ylim[1])
  poly = Polygon(rbind(corner1, c(corner1[1], corner2[2]), corner2, c(corner2[1], corner1[2]), corner1), hole = FALSE)
  return(SpatialPolygons(list(Polygons(list(poly), ID = runif(1)))))
}

poly1 = local.square.polygon(xlim=c(-1, 5-smalldist/2), 
                          ylim=5+width*c(-.5, .5))
poly2 = local.square.polygon(xlim=c(5+smalldist/2, 11), 
                          ylim=5+width*c(-.5, .5))
poly.original = SpatialPolygons(c(poly1@polygons, poly2@polygons))

plot(poly.original, main="Barrier area polygon")

First we set the input variables. Feel free to experiment with different values for smalldist and width, as changes can result in markedly different behavior.

We set up the mesh which will be the numerical/discrete representation of the spatial study region. We will not describe the process of mesh construction in this section. Good mesh construction is a somewhat involved process see BTopic104. Some of you may know how to construct good meshes already, e.g. from
the SPDE-tutorial.

max.edge.length = 0.4
# - The coarseness of the finite element approximation
# - Corresponds to grid-square width in discretisations
# - - Except that finite element approximations are better
# - Should be compared to size of study area
# - Should be less than a fourth of the estimated (posterior) 
#   spatial range
# - Up to 8x computational time when you halve this value
loc1 = matrix(c(0,0, 10,0, 0,10, 10,10), 4, 2, byrow = T)
# - This defines the extent of the interior part of the mesh
# - In an application, if you want the mesh to depend on your 
#   data locations, you may use those locations instead
seg = inla.sp2segment(poly.original)
# - Transforms a SpatialPolygon to an "inla polygon"
mesh = inla.mesh.2d(loc=loc1, interior = seg, 
                    max.e = max.edge.length, offset=1)
# - The INLA mesh constructor, used for any INLA-SPDE model

The interior argument of inla.mesh.2d makes sure that the triangles in the mesh respect the boundary of our barrier.

Next, we will pick out which triangles in the mesh belongs to the barrier area.

tl = length(mesh$graph$tv[,1])
# - the number of triangles in the mesh
posTri = matrix(0, tl, 2)
for (t in 1:tl){
  temp = mesh$loc[mesh$graph$tv[t, ], ]
  posTri[t,] = colMeans(temp)[c(1,2)] 
}
posTri = SpatialPoints(posTri)
# - the positions of the triangle centres

barrier = over(poly.original, SpatialPoints(posTri), returnList=T)
# - checking which mesh triangles are inside the barrier area
barrier = unlist(barrier)
poly.barrier = inla.barrier.polygon(mesh, barrier.triangles = barrier)
# - the Barrier model's polygon
# - in most cases this should be the same as poly.original

Then we define the Omega object which is the object that the code for the new Barrier model needs. We also define the poly.barrier which is the spatial polygon needed for plotting the boundaries of the barrier area.

At this point, it is crucial to check that our mesh, and the two areas (normal area and barrier area) are correct. We make sure to always use poly.barrier to plot the barrier from now on, since this is the same as what is used by the algorithm (which may not be true for our original polygon p).

In this figure we see the entire mesh. The grey background is for the barrier area. The light blur background is for the normal area. The blue and red squares are the boundaries of the barrier area. The black circles are our initial locations (here, we use them only to determine the extent of the mesh).

plot(mesh, main="Mesh and Omega")
plot(poly.barrier, add=T, col='lightblue')
plot(mesh, add=T)
points(loc1)

The triangles here are light blue (normal area) and grey (barrier area). The four circles are the mesh locations. The blue and red squares are a part of the mesh object.

Next we define a function defining how to plot any spatial fields (for this example). We have also defined the part of space that is to be plotted, in any future plots, namely xlim=ylim=c(2,8). (Since we only care about this region, the mesh extension is large enough. We discuss mesh extensions in a different topic)

local.plot.field = function(field, ...){
  xlim = c(2, 8); ylim = xlim;
  proj = inla.mesh.projector(mesh, xlim = xlim, 
                             ylim = ylim, dims=c(300, 300))
  # - Can project from the mesh onto a 300x300 grid 
  #   for plots
  field.proj = inla.mesh.project(proj, field)
  # - Do the projection
  image.plot(list(x = proj$x, y=proj$y, z = field.proj), 
             xlim = xlim, ylim = ylim, col = plasma(17), ...)  
  # - Use image.plot to get nice colors and legend
}
print(mesh$n)
## [1] 2613
# - This is the appropriate length of the field variable

The input variable field must have the same length as there are mesh nodes in the mesh.

Next, we compute the Finite Element matrices needed to solve the SPDE for the non-stationary Barrier model. \[\begin{align} u(s) - \nabla \cdot \frac{r^2}{8} \nabla u(s) &= r \sqrt{\frac{\pi}{2}} \sigma_u \mathcal{W}(s), \text{ for } s \in \Omega_n \\ u(s) - \nabla \cdot \frac{r_b^2}{8} \nabla u(s) &= r_b \sqrt{\frac{\pi}{2}} \sigma_u \mathcal{W}(s), \text{ for } s \in \Omega_b, \end{align}\] To learn more about these equations, see the paper on Arxiv (Bakka et al. 2016), and the appendix therein. Essentially, we are doing all the computations we can in advance, so that solving the system of equations happens quickly at each step of simulation and inference. We will not comment on the internal workings of this function now, but it will be described in detail in another topic.

barrier.model = inla.barrier.pcmatern(mesh, barrier.triangles = barrier, prior.range = c(1.44, 0.5), prior.sigma = c(0.7, 0.5), range.fraction = 0.1)
# - Set up the inla model, including the matrices for solving the SPDE

3 Simulate data

In this subsection, we simulate data. When simulating data, we are in a sense pretending to be nature, producing a set of observations for the human observers. We will do inference on this data further down on this page.

First we set the ranges, with a range 0.3 on the first region, i.e. the barrier, and a range of 3 in the second region, i.e. the normal region. The range is the distance at which correlation is essentially zero. Then we compute the precision matrix \(Q\) (inverse covariance matrix) for the spatial random field \[u \sim \mathcal N(0, Q^{-1})\]

range = 3
# - the spatial range parameter
Q = inla.rgeneric.q(barrier.model, "Q", theta = c(0, log(range)))
# - the precision matrix for fixed ranges
# - Q is a function of the hyperparameters theta = c( log(sigma), log(range1), log(range2),...)

Now, we sample a spatial field using the precision matrix \(Q\). This is done through a sparse Cholesky factorization (Rue and Held 2005). Then we plot that field, adding the barrier area in grey on top. The value of the field on the barrier is of no interest, as there can never be observations there.

u = inla.qsample(n=1, Q=Q, seed = set.inla.seed)
## Warning in inla.qsample(n = 1, Q = Q, seed = set.inla.seed): Since
## 'seed!=0', parallel model is disabled and serial model is selected
u = u[ ,1]
# - access the first sample

local.plot.field(u, main="The true (simulated) spatial field")
plot(poly.barrier, add=T, col='grey')

# - Overlay the barrier

Now we simulate the spatial locations and the data. We never have observation locations in the barrier region (e.g. no measurements of fish on land), so the easiest way to sample locations is to sample everywhere, and then just delete those samples that end up in the barrier region. The projector matrix is the function taking a field-defined-on-the-mesh into a field-defined-on-the-data-locations, see BTopic101.

num.try = 500 
# - try to sample this number of data locations
loc.try = matrix(runif(num.try*2, min=2, max=8), 
                         num.try, 2)
# - locations sampled inside the barrier will be removed 
#   in a few lines
temp = SpatialPoints(loc.try)
loc.ok = is.na(over(temp, poly.barrier))
# - only allow locations that are not inside the Barrier area
loc.data = loc.try[loc.ok, ]
A.data = inla.spde.make.A(mesh, loc.data)
# - the projector matrix required for any spatial model
# - this matrix can transform the field-defined-on-the-mesh 
#   to the field-defined-on-the-data-locations
c(dim(A.data), mesh$n, nrow(loc.data))
## [1]  469 2613 2613  469
# - shows that the dimensions are correct
u.data = A.data %*% u
# - project the field from the finite element  
#   representation to the data locations

Next we create the dataframe object.

df = data.frame(loc.data)
# - df is the dataframe used for modeling
names(df) = c('locx', 'locy')
sigma.u = 1
# - size of the random effect
# - feel free to change this value
sigma.epsilon = 0.2
# - size of the iid noise in the Gaussian likelihood
# - feel free to change this value
df$y = drop(sigma.u*u.data + sigma.epsilon*rnorm(nrow(df)))
# - sample observations with gaussian noise

We have now completed the simulation of the spatial dataset.

summary(df)
##       locx          locy           y       
##  Min.   :2.0   Min.   :2.0   Min.   :-3.7  
##  1st Qu.:3.4   1st Qu.:3.7   1st Qu.:-1.4  
##  Median :5.0   Median :5.5   Median :-0.3  
##  Mean   :5.0   Mean   :5.1   Mean   :-0.2  
##  3rd Qu.:6.5   3rd Qu.:6.7   3rd Qu.: 0.7  
##  Max.   :8.0   Max.   :8.0   Max.   : 4.7

This is the entire dataframe, and is the only object we ‘know’, besides the spatial mesh and barrier, when we are performing inference.

4 Stationary INLA model

The model in this subsection not the new model, but the standard spatial model in INLA. We add this for comparing the stationary and Barrier model. This means that the model in this subsection is not the model used for simulation (that model will come in the next subsection).

The stack will be used both for the stationary model and for the Barrier model. This is just a way of organizing the different variables. We could have written this example without using the stack, but if you want to use this model for anything more advanced, you will need this stack functionality.

stk <- inla.stack(data=list(y=df$y), A=list(A.data, 1),
                  effects=list(s=1:mesh$n, 
                               intercept=rep(1, nrow(df))), 
                  remove.unused = FALSE, tag='est')
# - this is the common stack used in INLA SPDE models
# - see the SPDE-tutorial
# - - http://www.r-inla.org/examples/tutorials/spde-tutorial

Next, we set up a standard INLA call for a stationary spatial effect. We use the PC prior for the Gaussian \(\sigma_\epsilon\). We start the numerical optimizer with a good value in control.mode, to reduce the computational time (you may remove this line, but it will take longer). We know where to start the optimizer because we have run this model one without that line. In your case, you may run a subset of your data to find good starting values, especially for space-time models.

model.stat = inla.spde2.pcmatern(mesh, prior.range = c(1, 0.5), prior.sigma = c(1, 0.5))
# - Set up the model component for the spatial SPDE model: 
#   Stationary Matern model
# - I assume you are somewhat familiar with this model

formula <- y ~ 0 + intercept + f(s, model=model.stat)
# - Remove the default intercept
# - - Having it in the stack instead improves the numerical 
#     accuracy of the INLA algorithm
# - Fixed effects + random effects

res.stationary <- inla(formula, data=inla.stack.data(stk),
            control.predictor=list(A = inla.stack.A(stk)),
            family = 'gaussian',
            control.family = list(hyper = list(prec = list(
              prior = "pc.prec", fixed = FALSE, 
              param = c(0.2,0.5)))),
            control.mode=list(restart=T, theta=c(4,-1.7,0.25)))

We look at the standard summary.

summary(res.stationary)
## 
## Call:
##    c("inla.core(formula = formula, family = family, contrasts = 
##    contrasts, ", " data = data, quantiles = quantiles, E = E, 
##    offset = offset, ", " scale = scale, weights = weights, 
##    Ntrials = Ntrials, strata = strata, ", " lp.scale = lp.scale, 
##    link.covariates = link.covariates, verbose = verbose, ", " 
##    lincomb = lincomb, selection = selection, control.compute = 
##    control.compute, ", " control.predictor = control.predictor, 
##    control.family = control.family, ", " control.inla = 
##    control.inla, control.fixed = control.fixed, ", " control.mode 
##    = control.mode, control.expert = control.expert, ", " 
##    control.hazard = control.hazard, control.lincomb = 
##    control.lincomb, ", " control.update = control.update, 
##    control.lp.scale = control.lp.scale, ", " control.pardiso = 
##    control.pardiso, only.hyperparam = only.hyperparam, ", " 
##    inla.call = inla.call, inla.arg = inla.arg, num.threads = 
##    num.threads, ", " blas.num.threads = blas.num.threads, keep = 
##    keep, working.directory = working.directory, ", " silent = 
##    silent, inla.mode = inla.mode, safe = FALSE, debug = debug, ", 
##    " .parent.frame = .parent.frame)") 
## Time used:
##     Pre = 3.47, Running = 2.47, Post = 0.0677, Total = 6 
## Fixed effects:
##            mean  sd 0.025quant 0.5quant 0.97quant  mode kld
## intercept -0.18 1.1       -2.5    -0.17         2 -0.17   0
## 
## Random effects:
##   Name     Model
##     s SPDE2 model
## 
## Model hyperparameters:
##                                          mean    sd 0.025quant
## Precision for the Gaussian observations 27.01 2.825      21.85
## Range for s                              4.79 1.362       2.99
## Stdev for s                              2.03 0.509       1.33
##                                         0.5quant 0.97quant  mode
## Precision for the Gaussian observations    26.88     32.70 26.62
## Range for s                                 4.51      8.01  3.91
## Stdev for s                                 1.93      3.22  1.70
## 
## Marginal log-Likelihood:  -190.87 
##  is computed 
## Posterior summaries for the linear predictor and the fitted values are computed
## (Posterior marginals needs also 'control.compute=list(return.marginals.predictor=TRUE)')

We plot the result of the stationary model in the next figure.

local.plot.field(res.stationary$summary.random$s$mean,
          main="Spatial estimate with the stationary model")
# - plot the posterior spatial marginal means
# - we call this the spatial estimate, or the smoothed data
plot(poly.barrier, add=T, col='grey')

# - Posterior spatial estimate using the stationary model

5 Barrier model

This is the same model as we used for simulating the data. Except that we have now “forgotten” the range \(r_b\) in the barrier area. This range is never known, so it is unreasonable to use it for inference. However, using any small value for the range in the barrier region is OK, it does not need to be the “true value”. For more information about this, see appendix “Choosing barrier range” in (Bakka et al. 2016). The Barrier model is a special case of the Different

First we set up extra variables needed for my implementation of the new model. My implementation is based on “rgeneric” in INLA, see \inlinecode{inla.doc("rgeneric")}. This part of the code defines what range parameters belong to each area.

formula2 <- y ~ 0 + intercept + f(s, model=barrier.model)
# - The spatial model component is different from before
# - The rest of the model setup is the same as in the stationary case!
# - - e.g. the inla(...) call below is the same, 
#     only this formula is different

Finally, we are ready to run inference on the simulated dataset. Similarly to the stationary case, you may remove the control.mode input. That will cause the model to run slightly slower. If you have trouble with inference in a different dataset, setting these values (the initial values for the algorithm) often solves the problem. Please note that these are the values for the internal parametrization, so \(\log(precision), \log(sigma), \log(range)\), see result$internal.summary.hyperpar.

res.barrier = inla(formula2, data=inla.stack.data(stk),
       control.predictor=list(A = inla.stack.A(stk)),
       family = 'gaussian',
       control.family = list(hyper = list(prec = list(
             prior = "pc.prec", fixed = FALSE, 
             param = c(0.2,0.5)))),
       control.mode=list(restart=T, theta=c(3.2, 0.4, 1.6)))
## Warning in inla.model.properties.generic(inla.trim.family(model), mm[names(mm) == : Model 'rgeneric' in section 'latent' is marked as 'experimental'; changes may appear at any time.
##   Use this model with extra care!!! Further warnings are disabled.
summary(res.barrier)
## 
## Call:
##    c("inla.core(formula = formula, family = family, contrasts = 
##    contrasts, ", " data = data, quantiles = quantiles, E = E, 
##    offset = offset, ", " scale = scale, weights = weights, 
##    Ntrials = Ntrials, strata = strata, ", " lp.scale = lp.scale, 
##    link.covariates = link.covariates, verbose = verbose, ", " 
##    lincomb = lincomb, selection = selection, control.compute = 
##    control.compute, ", " control.predictor = control.predictor, 
##    control.family = control.family, ", " control.inla = 
##    control.inla, control.fixed = control.fixed, ", " control.mode 
##    = control.mode, control.expert = control.expert, ", " 
##    control.hazard = control.hazard, control.lincomb = 
##    control.lincomb, ", " control.update = control.update, 
##    control.lp.scale = control.lp.scale, ", " control.pardiso = 
##    control.pardiso, only.hyperparam = only.hyperparam, ", " 
##    inla.call = inla.call, inla.arg = inla.arg, num.threads = 
##    num.threads, ", " blas.num.threads = blas.num.threads, keep = 
##    keep, working.directory = working.directory, ", " silent = 
##    silent, inla.mode = inla.mode, safe = FALSE, debug = debug, ", 
##    " .parent.frame = .parent.frame)") 
## Time used:
##     Pre = 2.94, Running = 5.09, Post = 0.285, Total = 8.31 
## Fixed effects:
##            mean  sd 0.025quant 0.5quant 0.97quant  mode kld
## intercept -0.16 1.2       -2.5    -0.17       2.1 -0.18   0
## 
## Random effects:
##   Name     Model
##     s RGeneric2
## 
## Model hyperparameters:
##                                           mean    sd 0.025quant
## Precision for the Gaussian observations 24.709 2.382     20.349
## Theta1 for s                             0.536 0.224      0.132
## Theta2 for s                             1.770 0.248      1.324
##                                         0.5quant 0.97quant   mode
## Precision for the Gaussian observations   24.596    29.500 24.371
## Theta1 for s                               0.523     0.989  0.472
## Theta2 for s                               1.756     2.272  1.699
## 
## Marginal log-Likelihood:  -142.00 
##  is computed 
## Posterior summaries for the linear predictor and the fitted values are computed
## (Posterior marginals needs also 'control.compute=list(return.marginals.predictor=TRUE)')

We plot the result of the Barrier model in the next figure.

local.plot.field(res.barrier$summary.random$s$mean, 
                 main="Spatial posterior for Barrier model")
# - plot the posterior spatial marginal means
# - we call this the spatial (smoothing) estimate
plot(poly.barrier, add=T, col='grey')

# - Posterior spatial estimate using the Barrier model

Compare this plot to the plot “The true (simulated) field” and to the stationary solution “Posterior spatial estimate using the stationary model”. Depending on the simulated field (i.e. the random seeds), these figures may look very much the same, or be very different. Our experience is that, even when the spatial means look quite different, there may be a big difference in model fit or prediction evaluations. Feel free to also plot the quantiles and sd of the two posterior fields. For a discussion of the results we refer you to the main paper .

5.0.1 Plotting the posterior of the hyper-parameters

For how to plot the posterior of the hyper-parameters in the stationary model, see BTopic108.

res.barrier$summary.hyperpar
##                                          mean   sd 0.025quant
## Precision for the Gaussian observations 24.71 2.38      20.35
## Theta1 for s                             0.54 0.22       0.13
## Theta2 for s                             1.77 0.25       1.32
##                                         0.5quant 0.97quant  mode
## Precision for the Gaussian observations    24.60     29.50 24.37
## Theta1 for s                                0.52      0.99  0.47
## Theta2 for s                                1.76      2.27  1.70

This shows the ordering of the hyper-parameters, and is how we know what index to use later in the plots.

To plot the hyper-parameters in the Barrier model, we must know the transformations. \[\begin{align} \sigma &= e^ {\theta_1} \quad \text{is the marginal standard deviation} \\ r &= e^ {\theta_2} \quad \text{is the spatial range} \end{align}\]

For the parameter \(\sigma\), the code is as follows (the dashed line is the prior, with an arbitrary scaling).

tmp = inla.tmarginal(function(x) exp(x), res.barrier$marginals.hyperpar[[2]]) 
plot(tmp, type = "l", xlab = "sigma", ylab = "Density")
xvals = seq(0, 10, length.out=1000)
lambda = 0.99; lines(xvals, 3*exp(-lambda*xvals), lty='dashed')
abline(v=1, col="blue")

For the parameter \(r\), the code is as follows (the dashed line is the prior, with an arbitrary scaling).

tmp = inla.tmarginal(function(x) exp(x), res.barrier$marginals.hyperpar[[3]]) 
plot(tmp, type = "l", xlab = "r", ylab = "Density")
xvals = seq(0, 10, length.out=1000)
lambda = 1.00; lines(xvals, 3*exp(-lambda*xvals), lty='dashed')
abline(v=range, col="blue")

6 Comments

6.1 Mistakes to avoid

I will not discuss potential mistakes from the definition of the mesh or the barrier, as that is included in other topics. Make sure that you do not mix up what area is the barrier, and what is the normal.

6.2 How to use this as a model component in other models

Here, I outline how to do things that are slightly different.

To add more covariates in the model, add them in the inla.stack. See the SPDE-book for more information on this .

You can easily use other likelihoods, by changing the argument of the -call. See for a description of these. Please make sure you choose good priors for any hyper-parameters in the likelihood (the choice we made in the code here is aarbitrary and not good).

You can also add different random effects to the formula (see info about latent models on ). When you use additional random effects, you must include them into the stack, similarly to how the intercept is here included in the stack. For examples of this, use the SPDE-book.

References

Bakka, H., J. Vanhatalo, J. Illian, D. Simpson, and H. Rue. 2016. “Accounting for Physical Barriers in Species Distribution Modeling with Non-Stationary Spatial Random Effects.” arXiv preprint arXiv:1608.03787. Norwegian University of Science; Technology, Trondheim, Norway.
Rue, Håvard, and Leonard Held. 2005. Gaussian Markov Random Fields: Theory and Applications. Vol. 104. Monographs on Statistics and Applied Probability. London: Chapman & Hall.