In this vignette we will present the R-INLA
implementation of the rational SPDE approach. For theoretical details we refer the reader to the Rational approximation with the rSPDE
package vignette and to (Bolin and Kirchner 2020)(https://www.tandfonline.com/doi/full/10.1080/10618600.2019.1665537).
We begin by providing a step-by-step illustration on how to use our implementation. To this end we will consider a real world data set that consists of precipitation measurements from the Paraná region in Brazil.
After the initial model fitting, we will show how to change some parameters of the model.
In the end, we will also provide an example in which we have replicates.
It is important to mention that one can improve the performance by using the PARDISO solver. Please, go to https://www.pardiso-project.org/r-inla/#license to apply for a license. Also, use inla.pardiso()
for instructions on how to enable the PARDISO sparse library.
To illustrate our implementation of rSPDE
in R-INLA
we will consider a dataset available in R-INLA
. This data has also been used to illustrate the SPDE approach, see for instance the book Advanced Spatial Modeling with Stochastic Partial Differential Equations Using R and INLA and also the vignette Spatial Statistics using R-INLA and Gaussian Markov random fields. See also (Lindgren, Rue, and Lindström 2011)(https://rss.onlinelibrary.wiley.com/doi/full/10.1111/j.1467-9868.2011.00777.x) for theoretical details on the standard SPDE approach.
The data consist of precipitation measurements from the Paraná region in Brazil and were provided by the Brazilian National Water Agency. The data were collected at 616 gauge stations in Paraná state, south of Brazil, for each day in 2011.
We will follow the vignette Spatial Statistics using R-INLA and Gaussian Markov random fields. As precipitation data are always positive, we will assume it is Gamma distributed. R-INLA
uses the following parameterization of the Gamma distribution, \[\Gamma(\mu, c\phi): \pi (y) = \frac{1}{\Gamma(c\phi)} \left(\frac{c\phi}{\mu}\right)^{c\phi} y^{c\phi - 1} \exp\left(-\frac{c\phi y}{\mu}\right) .\] In this parameterization, the distribution has expected value \(E(x) = \mu\) and variance \(V(x) = \mu^2/(c\phi)\), where \(c\) is a fixed (known) scaling parameter and \(1/\phi\) is a dispersion parameter.
In this example \(\mu\) will be modelled using a stochastic model that includes both covariates and spatial structure, resulting in the latent Gaussian model for the precipitation measurements \[\begin{align} y_i\mid \mu(s_i), \theta &\sim \Gamma(\mu(s_i),c\phi)\\ \log (\mu(s)) &= \eta(s) = \sum_k f_k(c_k(s))+u(s)\\ \theta &\sim \pi(\theta) \end{align},\]
where \(y_i\) denotes the measurement taken at location \(s_i\), \(c_k(s)\) are covariates, \(u(s)\) is a mean-zero Gaussian Matérn field, and \(\theta\) is a vector containing all parameters of the model, including smoothness of the field. That is, by using the rSPDE
model we will also be able to estimate the smoothness of the latent field.
We will be using R-INLA
. To install R-INLA
go to R-INLA Project.
We begin by loading some libraries we need to get the data and build the plots.
Let us load the data and the border of the region
The data frame contains daily measurements at 616 stations for the year 2011, as well as coordinates and altitude information for the measurement stations. We will not analyze the full spatio-temporal data set, but instead look at the total precipitation in January, which we calculate as
In the next snippet of code, we extract the coordinates and altitudes and remove the locations with missing values.
Let us build a plot for the precipitations:
ggplot() + geom_point(aes(x = coords[, 1], y = coords[, 2], colour = Y), size = 2,
alpha = 1) + scale_colour_gradientn(colours = tim.colors(100)) + geom_path(aes(x = PRborder[,
1], y = PRborder[, 2])) + geom_path(aes(x = PRborder[1034:1078, 1], y = PRborder[1034:1078,
2]), colour = "red")
The red line in the figure shows the coast line, and we expect the distance to the coast to be a good covariate for precipitation.
This covariate is not available, so let us calculate it for each observation location:
Now, let us plot the precipitation as a function of the possible covariates:
To use the R-INLA
implementation of the rSPDE
model we need to load the functions:
The rSPDE
-INLA
implementation is very reminiscent of R-INLA
, so its usage should be straightforward for R-INLA
users. For instance, to create a rSPDE
model, one would rspde.matern()
in place of inla.spde2.matern()
. To create an index, one should use rspde.make.index()
in place of inla.spde.make.index()
. To create the A
matrix, one should use rspde.make.A()
in place of inla.spde.make.A()
, and so on.
The main differences when comparing the arguments between the rSPDE
-INLA
implementation and the standard SPDE implementation in R-INLA
, are the nu
and rspde_order
arguments, which are present in rSPDE
-INLA
implementation. We will see below how use these arguments.
We can use R-INLA
for creating the mesh. Let us create a mesh which is based on a non-convex hull to avoid adding many small triangles outside the domain of interest:
We now create the \(A\) matrix, that connects the mesh to the observation locations and then create the rSPDE
model.
For this task, as we mentioned earlier, we need to use an rSPDE
specific function, whose name is very reminiscent to R-INLA
’s standard SPDE approach, namely rspde.make.A()
(in place of R-INLA
’s inla.spde.make.A()
). The reason for the need of this specific function is that the size of the \(A\) matrix depends on the order of the rational approximation. The details can be found in the introduction of the Rational approximation with the rSPDE
package vignette.
The default order is 2 for our covariance-based rational approximation. As mentioned in the introduction of the Rational approximation with the rSPDE
package vignette, an approximation of order 2 in the covariance-based rational approximation has approximately the same computational cost as the standard rational approximation of order 1.
Recall that the latent process \(u\) is a solution of \[(\kappa^2 I-\Delta)^{\alpha/2}(\tau u) = \mathcal{W},\] where \(\alpha = \nu + d/2\).
We want to estimate \(\tau,\kappa\) and \(\nu\).
There is also an option to fix the smoothness parameter \(\nu\) to some predefined value and only estimate \(\tau\) and \(\kappa\).
By default the rSPDE
-INLA
implementation estimates all the three parameters.
In this first example we will assume we want a rational approximation of order 2, where we also want to estimate the smoothness parameter \(\nu\).
To this end we can use the rspde.make.A()
function. Since we will assume order 2 and that we want to estimate smoothness, which are the default options in this function, the required parameters are simply the mesh and the locations:
To set up an rSPDE
model, all we need is the mesh. By default it will assume that we want to estimate the smoothness parameter \(\nu\) and to do a covariance-based rational approximation of order 2.
Later in this vignette we will also see other options for setting up rSPDE
models such as keeping the smoothness parameter fixed and/or increasing the order of the covariance-based rational approximation.
Therefore, to set up a model all we have to do is use the rspde.matern()
function:
Notice that this function is very reminiscent of R-INLA
’s inla.spde2.matern()
function.
This is a pattern we tried to keep consistent. All the rSPDE
versions of some R-INLA
function will either replace inla
or inla.spde
or inla.spde2
by rspde
.
inla.stack
Since the covariates are already evaluated at the observation locations, we only want to apply the \(A\) matrix to the spatial effect and not the fixed effects. We can use the inla.stack()
function.
The difference, however, is that we need to use the function rspde.make.index()
(in place of the standard inla.spde.make.index()
) to create the index.
If one is using the default options, that is, to estimate the smoothness parameter \(\nu\) and to do a rational approximation of order 2, the usage of rspde.make.index()
is identical to the usage of inla.spde.make.index()
:
We can then create the stack in a standard manner:
stk.dat <- inla.stack(
data = list(y = Y), A = list(Abar, 1), tag = "est",
effects = list(c(mesh.index,
list(Intercept = 1)),
list(long = inla.group(coords[, 1]),
lat = inla.group(coords[,2]),
seaDist = inla.group(seaDist))))
Here the observation matrix \(A\) is applied to the spatial effect and the intercept while an identity observation matrix, denoted by \(1\), is applied to the covariates. This means the covariates are unaffected by the observation matrix.
The observation matrices in \(A=list(Abar,1)\) are used to link the corresponding elements in the effects-list to the observations. Thus in our model the latent spatial field mesh.index
and the intercept are linked to the log-expectation of the observations, i.e. \(\eta(s)\), through the \(A\)-matrix. The covariates, on the other hand, are linked directly to \(\eta(s)\). The stk.dat
object defined above implies the following principal linkage between model components and observations \[\eta(s) \sim A x(s) + A \text{ Intercept} + \text{long} + \text{lat}+ \text{seaDist}.\] \(\eta(s)\) will then be used in the observation-likelihood, \[y_i\mid \eta(s_i),\theta \sim \Gamma(\exp(\eta (s_i)), c\phi).\]
We will build a model using the distance to the sea \(x_i\) as a covariate through an improper CAR(1) model with \(\beta_{ij}=1(i\sim j)\), which R-INLA
calls a random walk of order 1.
Here -1
is added to remove R’s implicit intercept, which is replaced by the explicit +Intercept
from when we created the stack.
To fit the model we proceed as in the standard SPDE approach and we simply call inla()
. It is noteworthy that we can have a performance enhancement by using the argument inla.mode = "experimental"
. As argued in R-INLA
’s man page, not all features are available in inla.mode="experimental"
, and we will need to use the "classic"
mode (the default mode) instead of "experimental"
whenever we are doing predictions.
We can look at some summaries of the posterior distributions for the parameters, for example the fixed effects (i.e. the intercept) and the hyper-parameters (i.e. dispersion in the gamma likelihood, the precision of the RW1, and the parameters of the spatial field):
##
## 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.81, Running = 24.6, Post = 0.0734, Total = 28.5
## Fixed effects:
## mean sd 0.025quant 0.5quant 0.975quant mode kld
## Intercept 0.648 0.02 0.609 0.648 0.687 0.648 0
##
## Random effects:
## Name Model
## seaDist RW1 model
## field RGeneric2
##
## Model hyperparameters:
## mean sd 0.025quant
## Precision parameter for the Gamma observations 13.12 0.900 11.444
## Precision for seaDist 9032.84 6385.059 2408.642
## Theta1 for field -1.25 0.497 -2.234
## Theta2 for field 1.09 0.251 0.599
## Theta3 for field -0.82 0.342 -1.488
## 0.5quant 0.975quant mode
## Precision parameter for the Gamma observations 13.085 14.981 13.019
## Precision for seaDist 7298.453 25848.274 5034.889
## Theta1 for field -1.244 -0.273 -1.235
## Theta2 for field 1.088 1.589 1.081
## Theta3 for field -0.823 -0.138 -0.834
##
## Marginal log-Likelihood: -1262.43
## Posterior summaries for the linear predictor and the fitted values are computed
## (Posterior marginals needs also 'control.compute=list(return.marginals.predictor=TRUE)')
Let \(\theta_1 = \textrm{Theta1}\), \(\theta_2=\textrm{Theta2}\) and \(\theta_3=\textrm{Theta3}\). In terms of the SPDE \[(\kappa^2 I - \Delta)^{\alpha/2}(\tau u) = \mathcal{W},\] where \(\alpha = \nu + d/2\), we have that \[\tau = \exp(\theta_1),\quad \kappa = \exp(\theta_2), \] and by default \[\nu = 4\Big(\frac{\exp(\theta_3)}{1+\exp(\theta_3)}\Big).\] The number 4 comes from the upper bound for \(\nu\), which will be discussed later in this vignette.
In general, we have \[\nu = \nu_{UB}\Big(\frac{\exp(\theta_3)}{1+\exp(\theta_3)}\Big),\] where \(\nu_{UB}\) is the value of the upper bound for the smoothness parameter \(\nu\).
Another choice for prior for \(\nu\) is a truncated lognormal distribution and will also be discussed later in this vignette.
rSPDE
-INLA
resultsWe can obtain outputs with respect to parameters in the original scale by using the function rspde.result()
:
## mean sd 0.025quant 0.5quant 0.975quant mode
## tau 0.324522 0.168490 0.108411 0.288546 0.760979 0.225534
## kappa 3.064570 0.779383 1.829630 2.968680 4.894780 2.775470
## nu 1.239940 0.285905 0.741572 1.220750 1.860740 1.171030
We can also plot the posterior densities:
This function is reminiscent to the inla.spde.result()
function with the main difference that it has the summary()
and plot()
methods implemented.
Let us now obtain predictions (i.e. do kriging) of the expected precipitation on a dense grid in the region.
We begin by creating the grid in which we want to do the predictions. To this end, we can use the rspde.mesh.projector()
function. This function has the same arguments as the function inla.mesh.projector()
, with the only difference being that the rSPDE
version also has an argument nu
and an argument rspde_order
. Thus, we proceed in the same fashion as we would in R-INLA
’s standard SPDE implementation:
nxy <- c(150, 100)
projgrid <- rspde.mesh.projector(prmesh, xlim = range(PRborder[, 1]),
ylim = range(PRborder[,2]), dims = nxy)
This lattice contains 150 × 100 locations. One can easily change the resolution of the kriging prediction by changing nxy
. Let us find the cells that are outside the region of interest so that we do not plot the estimates there.
Let us plot the locations that we will do prediction:
coord.prd <- projgrid$lattice$loc[xy.in, ]
plot(coord.prd, type = "p", cex = 0.1)
lines(PRborder)
points(coords[, 1], coords[, 2], pch = 19, cex = 0.5, col = "red")
Now, there are a few ways we could calculate the kriging prediction. The simplest way is to evaluate the mean of all individual random effects in the linear predictor and then to calculate the exponential of their sum (since \(\mu(s)=\exp(\eta(s))\) ). A more accurate way is to calculate the prediction jointly with the estimation, which unfortunately is quite computationally expensive if we do prediction on a fine grid. However, in this illustration, we proceed with this option to show how one can do it.
To this end, first, link the prediction coordinates to the mesh nodes through an \(A\) matrix
Since we are using distance to the sea as a covariate, we also have to calculate this covariate for the prediction locations.
We now make a stack for the prediction locations. We have no data at the prediction locations, so we set y= NA
. We then join this stack with the estimation stack.
ef.prd = list(c(mesh.index, list(Intercept = 1)),
list(long = inla.group(coord.prd[,
1]), lat = inla.group(coord.prd[, 2]),
seaDist = inla.group(seaDist.prd)))
stk.prd <- inla.stack(data = list(y = NA),
A = list(A.prd, 1), tag = "prd",
effects = ef.prd)
stk.all <- inla.stack(stk.dat, stk.prd)
Doing the joint estimation takes a while, and we therefore turn off the computation of certain things that we are not interested in, such as the marginals for the random effect. We will also use a simplified integration strategy (actually only using the posterior mode of the hyper-parameters) through the command control.inla = list(int.strategy = "eb")
, i.e. empirical Bayes. Since we are doing predictions, we should not use R-INLA
’s experimental mode.
rspde_fitprd <- inla(f.s, family = "Gamma",
data = inla.stack.data(stk.all),
control.predictor = list(A = inla.stack.A(stk.all),
compute = TRUE, link = 1),
control.compute = list(return.marginals = FALSE,
return.marginals.predictor = FALSE),
control.inla = list(int.strategy = "eb"))
We then extract the indices to the prediction nodes and then extract the mean and the standard deviation of the response:
id.prd <- inla.stack.index(stk.all, "prd")$data
sd.prd <- m.prd <- matrix(NA, nxy[1], nxy[2])
m.prd[xy.in] <- rspde_fitprd$summary.fitted.values$mean[id.prd]
sd.prd[xy.in] <- rspde_fitprd$summary.fitted.values$sd[id.prd]
Finally, we plot the results:
rSPDE
-INLA
implementationWe will now discuss some of the arguments that were introduced in our R-INLA
implementation of the rational approximation that are not present in R-INLA
’s standard SPDE implementation.
In each case we will provide an illustrative example.
When we fit a rspde.matern()
model we need to provide an upper bound for the smoothness parameter \(\nu\). The reason for that is that the sparsity of the precision matrix should be kept fixed during R-INLA
’s estimation and the higher the value of \(\nu\) the denser the precision matrix gets.
This means that the higher the value of \(\nu\), the higher the computational cost to fit the model. Therefore, ideally, want to choose an upper bound for \(\nu\) as small as possible.
To change the value of the upper bound for the smoothness parameter, we must change the argument nu_upper_bound
. The default value for nu_upper_bound
is 4. Other common choices for nu_upper_bound
are 2 and 1.
It is clear from the discussion above that the smaller the value of nu_upper_bound
the faster the estimation procedure will be.
However, if we choose a value of nu_upper_bound
which is too low, the “correct” value of \(\nu\) might not belong to the interval \((0,\nu_{UB})\), where \(\nu_{UB}\) is the value of nu_upper_bound
. Hence, one might be forced to increase nu_upper_bound
and estimate again, which, obviously will increase the computational cost as we will need to do more than one estimation.
Let us illustrate by considering the same model we considered above for the precipitation in Paraná region in Brazil and consider nu_upper_bound
equal to 2, which is generally a good choice for nu_upper_bound
.
We simply use the function rspde.matern()
with the argument nu_upper_bound
set to 2:
Since we are considering the default rspde_order
, the \(A\) matrix and the mesh index objects are the same as the previous ones. Let us then update the formula and fit the model:
f.s.2 <- y ~ -1 + Intercept + f(seaDist, model = "rw1") +
f(field, model = rspde_model_2)
rspde_fit_2 <- inla(f.s.2, family = "Gamma", data = inla.stack.data(stk.dat),
verbose = FALSE,
control.inla=list(int.strategy='eb'),
control.predictor = list(A = inla.stack.A(stk.dat), compute = TRUE),
inla.mode = "experimental")
Let us see the summary of the fit:
##
## 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.42, Running = 10.9, Post = 0.0708, Total = 14.4
## Fixed effects:
## mean sd 0.025quant 0.5quant 0.975quant mode kld
## Intercept 0.647 0.018 0.612 0.647 0.683 0.647 0
##
## Random effects:
## Name Model
## seaDist RW1 model
## field RGeneric2
##
## Model hyperparameters:
## mean sd 0.025quant
## Precision parameter for the Gamma observations 13.222 0.902 11.508
## Precision for seaDist 11364.219 9237.811 2567.792
## Theta1 for field -1.605 0.680 -3.038
## Theta2 for field 1.235 0.286 0.694
## Theta3 for field 0.708 0.670 -0.509
## 0.5quant 0.975quant mode
## Precision parameter for the Gamma observations 13.203 15.055 13.181
## Precision for seaDist 8732.035 35683.064 5588.275
## Theta1 for field -1.562 -0.378 -1.407
## Theta2 for field 1.226 1.822 1.191
## Theta3 for field 0.665 2.128 0.511
##
## Marginal log-Likelihood: -1261.09
## Posterior summaries for the linear predictor and the fitted values are computed
## (Posterior marginals needs also 'control.compute=list(return.marginals.predictor=TRUE)')
Let us compare with the cost from the previous fit, with the default value of nu_upper_bound
of 4:
## Pre Running Post Total
## 3.81291890 24.61083293 0.07338619 28.49713802
## Pre Running Post Total
## 3.41834283 10.90437913 0.07079482 14.39351678
We can see that the fit for nu_upper_bound
equal to 2 was considerably faster.
Finally, let us get the result the user’s scale and see the estimate for \(\nu\):
## mean sd 0.025quant 0.5quant 0.975quant mode
## tau 0.248393 0.166655 0.0484216 0.210078 0.684151 0.129364
## kappa 3.580310 1.061880 2.0138000 3.407450 6.180330 3.075260
## nu 1.307970 0.271990 0.7591500 1.320650 1.786460 1.342520
To change the order of the rational approximation all we have to do is set the argument rspde_order
to the desired value. The current available possibilities are 1,2,3
,…, up to 8
.
The higher the order of the rational approximation, the more accurate the results will be, however, the higher the computational cost will be.
The default rspde_order
of 2 is generally a good choice and reasonably accurate. See the vignette Rational approximation with the rSPDE package for further details on the order of the rational approximation and some comparison with the Matérn covariance.
Let us fit the above model with the covariance-based rational approximation of order 3
. Notice that since we are changing the order of the rational approximation, that is, we are changing the rspde_order
argument, we need to recompute the \(A\) matrix and the mesh index. Therefore, we proceed as follows:
Now the remaining is standard:
stk.dat.3 <- inla.stack(
data = list(y = Y), A = list(Abar_3, 1), tag = "est",
effects = list(c(mesh.index.3,
list(Intercept = 1)),
list(long = inla.group(coords[, 1]),
lat = inla.group(coords[,2]),
seaDist = inla.group(seaDist))))
f.s.3 <- y ~ -1 + Intercept + f(seaDist, model = "rw1") +
f(field, model = rspde_model_order_3)
rspde_fit_order_3 <- inla(f.s.3, family = "Gamma", data = inla.stack.data(stk.dat.3),
verbose = FALSE,
control.inla=list(int.strategy='eb'),
control.predictor = list(A = inla.stack.A(stk.dat.3), compute = TRUE),
inla.mode = "experimental")
Let us see the summary:
##
## 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.41, Running = 15.7, Post = 0.0743, Total = 19.2
## Fixed effects:
## mean sd 0.025quant 0.5quant 0.975quant mode kld
## Intercept 0.486 0.014 0.459 0.486 0.512 0.486 0
##
## Random effects:
## Name Model
## seaDist RW1 model
## field RGeneric2
##
## Model hyperparameters:
## mean sd 0.025quant
## Precision parameter for the Gamma observations 13.298 0.874 11.671
## Precision for seaDist 7981.217 3876.395 2939.829
## Theta1 for field -1.344 0.522 -2.248
## Theta2 for field 1.137 0.303 0.500
## Theta3 for field 0.436 0.584 -0.837
## 0.5quant 0.975quant mode
## Precision parameter for the Gamma observations 13.266 15.106 13.199
## Precision for seaDist 7163.368 17842.084 5804.884
## Theta1 for field -1.389 -0.212 -1.552
## Theta2 for field 1.154 1.692 1.216
## Theta3 for field 0.486 1.468 0.667
##
## Marginal log-Likelihood: -1261.13
## Posterior summaries for the linear predictor and the fitted values are computed
## (Posterior marginals needs also 'control.compute=list(return.marginals.predictor=TRUE)')
We can see in the above summary that the computational cost significantly increased. Let us compare the cost of having rspde_order=3
and nu_upper_bound=2
with the cost of having rspde_order=2
and nu_upper_bound=4
:
## Pre Running Post Total
## 3.81291890 24.61083293 0.07338619 28.49713802
## Pre Running Post Total
## 3.40732598 15.70386314 0.07432795 19.18551707
Let us now see the summary in the original scale:
result_fit_order_3 <- rspde.result(rspde_fit_order_3, "field", rspde_model_order_3)
summary(result_fit_order_3)
## mean sd 0.025quant 0.5quant 0.975quant mode
## tau 0.300743 0.184596 0.106358 0.249145 0.807561 0.181173
## kappa 3.254920 0.961775 1.659980 3.172960 5.416470 2.985810
## nu 1.200700 0.264269 0.613235 1.239340 1.619390 1.358260
Let us see the plots of the posterior marginal densities:
We can fix the smoothness, say \(\nu\), of the model by providing a non-NULL positive value for nu
.
When the smoothness, \(\nu\), is fixed, we can have two possibilities:
\(\alpha = \nu + d/2\) is integer;
\(\alpha = \nu + d/2\) is not integer.
The first case, i.e., when \(\alpha\) is integer, has much less computational cost. Furthermore, the \(A\) matrix is different than the \(A\) matrix for the non-integer \(\alpha\).
The \(A\) matrix is the same for all values of \(\nu\) such that \(\alpha\) is integer. So, the \(A\) matrix for these cases only need to be computed once. The same holds for the index
obtained from the rspde.make.index()
function.
In the second case the \(A\) matrix only depends on the order of the rational approximation and not on \(\nu\). Therefore, if the matrix \(A\) has already been computed for some rspde_order
, then the \(A\) matrix will be same for all the values of \(\nu\) such that \(\alpha\) is non-integer for that rspde_order
. The same holds for the index
obtained from the rspde.make.index()
function.
If \(\nu\) is fixed, we have that the parameters returned by R-INLA
are \[\kappa = \exp(\theta_1)\quad\hbox{and}\quad\tau = \exp(\theta_2).\] We will now provide illustrations for both scenarios. It is also noteworthy that just as with the case in which we estimate \(\nu\), we can also change the order of the rational approximation by changing the value of rspde_order
. For both illustrations with fixed \(\nu\) below, we will only consider the order of the rational approximation of 2, that is, the default order.
Recall that: \[\nu = \nu_{UB}\Big(\frac{\exp(\theta_3)}{1+\exp(\theta_3)}\Big).\] Thus, to illustrate, let us consider a fixed \(\nu\) given by the mean of \(\nu\) obtained from the first model we considered in this vignette, namely, the fit given by rspde_fit
, which is approximately \(\nu = 1.21\).
Notice that for this \(\nu\), the value of \(\alpha\) is non-integer, so we can use the \(A\) matrix and the index of the first fitted model, which is also of order 2.
Therefore, all we have to do is build a new model in which we set nu
to 1.21
:
Let us now fit the model:
f.s.fix <- y ~ -1 + Intercept + f(seaDist, model = "rw1") +
f(field, model = rspde_model_fix)
rspde_fix <- inla(f.s.fix, family = "Gamma", data = inla.stack.data(stk.dat),
verbose = FALSE,
control.inla=list(int.strategy='eb'),
control.predictor = list(A = inla.stack.A(stk.dat), compute = TRUE),
inla.mode = "experimental")
Here we have the summary:
##
## 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.41, Running = 10.9, Post = 0.0647, Total = 14.3
## Fixed effects:
## mean sd 0.025quant 0.5quant 0.975quant mode kld
## Intercept 0.648 0.018 0.613 0.648 0.684 0.648 0
##
## Random effects:
## Name Model
## seaDist RW1 model
## field RGeneric2
##
## Model hyperparameters:
## mean sd 0.025quant
## Precision parameter for the Gamma observations 13.22 0.894 11.534
## Precision for seaDist 10474.90 9692.570 2436.256
## Theta1 for field -1.32 0.301 -1.918
## Theta2 for field 1.17 0.285 0.614
## 0.5quant 0.975quant mode
## Precision parameter for the Gamma observations 13.20 15.052 13.16
## Precision for seaDist 7564.73 35855.932 4677.86
## Theta1 for field -1.32 -0.735 -1.32
## Theta2 for field 1.17 1.734 1.18
##
## Marginal log-Likelihood: -1261.57
## Posterior summaries for the linear predictor and the fitted values are computed
## (Posterior marginals needs also 'control.compute=list(return.marginals.predictor=TRUE)')
Now, the summary in the original scale:
## mean sd 0.025quant 0.5quant 0.975quant mode
## tau 0.27772 0.0845032 0.147495 0.266291 0.479205 0.243636
## kappa 3.36292 0.9687290 1.852860 3.237540 5.658060 2.988170
Since we are in dimension \(d=2\), and \(\nu>0\), the smallest value of \(\nu\) that makes \(\alpha = \nu + 1\) an integer is \(\nu=1\). This value is also close to the estimated mean of the first model we fitted and enclosed by the posterior marginal density of \(\nu\) for the first fit. Therefore, let us fit the model with \(\nu=1\).
To this end we need to compute a new \(A\) matrix:
a new index:
create a new model (remember to set nu=1
):
The remaining is standard:
stk.dat.int <- inla.stack(
data = list(y = Y), A = list(Abar.int, 1), tag = "est",
effects = list(c(mesh.index.int,
list(Intercept = 1)),
list(long = inla.group(coords[, 1]),
lat = inla.group(coords[,2]),
seaDist = inla.group(seaDist))))
f.s.fix.int.1 <- y ~ -1 + Intercept + f(seaDist, model = "rw1") +
f(field, model = rspde_model_fix_int1)
rspde_fix_int_1 <- inla(f.s.fix.int.1, family = "Gamma",
data = inla.stack.data(stk.dat.int), verbose = FALSE,
control.inla=list(int.strategy='eb'),
control.predictor = list(A = inla.stack.A(stk.dat.int),
compute = TRUE),
inla.mode = "experimental")
Let us check the summary:
##
## 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.37, Running = 4.79, Post = 0.0502, Total = 8.21
## Fixed effects:
## mean sd 0.025quant 0.5quant 0.975quant mode kld
## Intercept 1.945 0.054 1.838 1.945 2.051 1.945 0
##
## Random effects:
## Name Model
## seaDist RW1 model
## field RGeneric2
##
## Model hyperparameters:
## mean sd 0.025quant
## Precision parameter for the Gamma observations 13.288 8.99e-01 11.587
## Precision for seaDist 11332.389 1.10e+04 2510.966
## Theta1 for field -0.894 2.74e-01 -1.419
## Theta2 for field 1.058 3.14e-01 0.421
## 0.5quant 0.975quant mode
## Precision parameter for the Gamma observations 13.266 15.126 13.232
## Precision for seaDist 8006.545 40070.631 4839.021
## Theta1 for field -0.899 -0.343 -0.919
## Theta2 for field 1.066 1.658 1.095
##
## Marginal log-Likelihood: -1260.81
## Posterior summaries for the linear predictor and the fitted values are computed
## (Posterior marginals needs also 'control.compute=list(return.marginals.predictor=TRUE)')
and check the summary in the user’s scale:
rspde_result_int <- rspde.result(rspde_fix_int_1, "field", rspde_model_fix_int1)
summary(rspde_result_int)
## mean sd 0.025quant 0.5quant 0.975quant mode
## tau 0.424374 0.119268 0.242743 0.40701 0.711337 0.372884
## kappa 3.020290 0.947769 1.530960 2.90529 5.244990 2.671680
We begin by recalling that the fitted rSPDE
model in R-INLA
contains the parameters \(\textrm{Theta1}\), \(\textrm{Theta2}\) and \(\textrm{Theta3}\). Let, again, \(\theta_1 = \textrm{Theta1}\), \(\theta_2=\textrm{Theta2}\) and \(\theta_3=\textrm{Theta3}\). In terms of the SPDE \[(\kappa^2 I - \Delta)^{\alpha/2}(\tau u) = \mathcal{W},\] where \(\alpha = \nu + d/2\).
We begin by dealing with \(\tau\) and \(\kappa\).
We have that \[\tau = \exp(\theta_1),\quad \kappa = \exp(\theta_2).\] The rspde.matern()
function assumes a lognormal prior distribution for \(\tau\) and \(\kappa\). This prior distribution is obtained by assuming that \(\theta_1\) and \(\theta_2\) follow normal distributions. By default we assume \(\theta_1\) and \(\theta_2\) to be independent and to follow normal distributions \(\theta_1\sim N(\log(\tau_0), 1)\) and \(\theta_2\sim N(\log(\kappa_0), 1)\).
\(\kappa_0\) is suitably defined in terms of the mesh and \(\tau_0\) is defined in terms of \(\kappa_0\) and on the prior of the smoothness parameter.
If one wants to define the prior \[\theta_1 \sim N(\text{mean_theta_1}, \text{sd_theta_1}),\] one can simply set the argument prior.tau = list(meanlog=mean_theta_1, sdlog=sd_theta_1)
. Analogously, to define the prior \[\theta_2 \sim N(\text{mean_theta_2}, \text{sd_theta_2}),\] one can set the argument prior.kappa = list(meanlog=mean_theta_2, sdlog=sd_theta_2)
.
It is important to mention that, by default, the initial values of \(\tau\) and \(\kappa\) are \(\exp(\text{mean_theta_1})\) and \(\exp(\text{mean_theta_2})\), respectively. So, if the user does not change these parameters, and also does not change the initial values, the initial values of \(\tau\) and \(\kappa\) will be, respectively, \(\tau_0\) and \(\kappa_0\).
If one sets prior.tau = list(meanlog=mean_theta_1)
, the prior for \(\theta_1\) will be \[\theta_1 \sim N(\text{mean_theta_1}, 1),\] whereas, if one sets, prior.tau = list(sdlog=sd_theta_1)
, the prior will be \[\theta_1 \sim N(\log(\tau_0), \text{sd_theta_1}).\] Analogously, if one sets prior.kappa = list(meanlog=mean_theta_2)
, the prior for \(\theta_2\) will be \[\theta_2 \sim N(\text{mean_theta_2}, 1),\] whereas, if one sets, prior.kappa = list(sdlog=sd_theta_2)
, the prior will be \[\theta_2 \sim N(\log(\kappa_0), \text{sd_theta_2}).\]
Finally, let us consider the smoothness parameter \(\nu\).
By default, we assume that \(\nu\) follows a beta distribution on the interval \((0,\nu_{UB})\), where \(\nu_{UB}\) is the upper bound for \(\nu\), with mean \(\nu_0=\min\{1, \nu_{UB}/2\}\) and variance \(\frac{\nu_0(\nu_{UB}-\nu_0)}{1+\phi_0}\), and we call \(\phi_0\) the precision parameter, whose default value is \[\phi_0 = \max\Big\{\frac{\nu_{UB}}{\nu_0}, \frac{\nu_{UB}}{\nu_{UB}-\nu_0}\Big\} + \phi_{inc}.\] The parameter \(\phi_{inc}\) is an increment to ensure that the prior beta density has boundary values equal to zero (where the boundary values are defined either by continuity or by limits). The default value of \(\phi_{inc}\) is 1. The value of \(\phi_{inc}\) can be changed by changing the argument nu.prec.inc
in the rspde.matern()
function. The higher the value of \(\phi_{inc}\) (that is, the value of nu.prec.inc
) the more informative the prior distribution becomes.
Let us denote a beta distribution with support on \((0,\nu_{UB})\), mean \(\mu\) and precision parameter \(\phi\) by \(\mathcal{B}_{\nu_{UB}}(\mu,\phi)\).
If we want \(\nu\) to have a prior \[\nu \sim \mathcal{B}_{\nu_{UB}}(\text{nu_1},\text{prec_1}),\] one simply needs to set prior.nu = list(mean=nu_1, prec=prec_1)
. If one sets prior.nu = list(mean=nu_1)
, then \(\nu\) will have prior \[\nu \sim \mathcal{B}_{\nu_{UB}}(\text{nu_1},\phi_1),\] where \[\phi_1 = \max\Big\{\frac{\nu_{UB}}{\text{nu_1}}, \frac{\nu_{UB}}{\nu_{UB}-\text{nu_1}}\Big\} + \text{nu.prec.inc}.\]
Of one sets prior.nu = list(prec=prec_1)
, then \(\nu\) will have prior \[\nu\sim \mathcal{B}_{\nu_{UB}}(\nu_0, \text{prec_1}).\] It is also noteworthy that we have that, in terms of R-INLA
’s parameters,
\[\nu = \nu_{UB}\Big(\frac{\exp(\theta_3)}{1+\exp(\theta_3)}\Big).\]
It is important to mention that, by default, if a beta prior distribution is chosen for the smoothness parameter \(\nu\), then the initial value of \(\nu\) is the mean of the prior beta distribution. So, if the user does not change this parameter, and also does not change the initial value, the initial value of \(\nu\) will be \(\min\{1,\nu_{UB}/2\}\).
We can have another possibility of prior distribution for \(\nu\), namely, a truncated lognormal distribution. The truncated lognormal distribution is defined in the following sense. We assume that \(\log(\nu)\) has prior distribution given by a truncated normal distribution with support \((-\infty,\log(\nu_{UB}))\), where \(\nu_{UB}\) is the upper bound for \(\nu\), with location parameter \(\mu_0 =\log(\nu_0)= \log\Big(\min\{1,\nu_{UB}/2\}\Big)\) and scale parameter \(\sigma_0 = 1\). More precisely, let \(\Phi(\cdot; \mu,\sigma)\) stand for the cumulative distribution function (CDF) of a normal distribution with mean \(\mu\) and standard deviation \(\sigma\). Then, \(\log(\nu)\) has cumulative distribution function given by \[F_{\log(\nu)}(x) = \frac{\Phi(x;\mu_0,\sigma_0)}{\Phi(\nu_{UB})},\quad x\leq \nu_{UB},\] and \(F_{\log(\nu)}(x) = 1\) if \(x>\nu_{UB}\). We will call \(\mu_0\) and \(\sigma_0\) the log-location and log-scale parameters of \(\nu\), respectively, and we say that \(\log(\nu)\) follows a truncated normal distribution with location parameter \(\mu_0\) and scale parameter \(\sigma_0\).
We also assume that, in terms of R-INLA
’s parameters, \[\nu = \nu_{UB}\Big(\frac{\exp(\theta_3)}{1+\exp(\theta_3)}\Big).\]
To change the prior distribution of \(\nu\) to the truncated lognormal distribution, we need to set the argument prior.nu.dist="lognormal"
.
To change these parameters in the prior distribution to, say, log_nu_1
and log_sigma_1
, one can simply set prior.nu = list(loglocation=log_nu_1, logscale=sigma_1)
.
If one sets prior.nu = list(loglocation=log_nu_1)
, the prior for \(\theta_3\) will be a truncated normal normal distribution with location parameter log_nu_1
and scale parameter 1
. Analogously, if one sets, prior.nu = list(logscale=sigma_1)
, the prior for \(\theta_3\) will be a truncated normal distribution with location parameter \(\log(\nu_0)= \log\Big(\min\{1,\nu_{UB}/2\}\Big)\) and scale parameter sigma_1
.
It is important to mention that, by default, if a truncated lognormal prior distribution is chosen for the smoothness parameter \(\nu\), then the initial value of \(\nu\) is the exponential of the log-location parameter of \(\nu\). So, if the user does not change this parameter, and also does not change the initial value, the initial value of \(\nu\) will be \(\min\{1,\nu_{UB}/2\}\).
Let us consider an example with the same dataset used in the first model of this vignette where we change the prior distribution of \(\nu\) from beta to lognormal. Let us also set nu_upper_bound
to 2.
Since we did not change rspde_order
and are not fixing \(\nu\), we can use the same \(A\) matrix and same index from the first example.
Therefore, all we have to do is update the formula and fit the model:
f.s.ln <- y ~ -1 + Intercept + f(seaDist, model = "rw1") +
f(field, model = rspde_model_ln)
rspde_fit_ln <- inla(f.s.ln, family = "Gamma", data = inla.stack.data(stk.dat),
verbose = FALSE,
control.inla=list(int.strategy='eb'),
control.predictor = list(A = inla.stack.A(stk.dat), compute = TRUE),
inla.mode = "experimental")
We have the summary:
##
## 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.51, Running = 11.8, Post = 0.0694, Total = 15.3
## Fixed effects:
## mean sd 0.025quant 0.5quant 0.975quant mode kld
## Intercept 0.648 0.019 0.611 0.648 0.685 0.648 0
##
## Random effects:
## Name Model
## seaDist RW1 model
## field RGeneric2
##
## Model hyperparameters:
## mean sd 0.025quant
## Precision parameter for the Gamma observations 13.156 0.877 11.528
## Precision for seaDist 10699.048 8932.884 2667.030
## Theta1 for field -1.456 0.760 -2.811
## Theta2 for field 1.136 0.329 0.445
## Theta3 for field 0.599 0.776 -1.058
## 0.5quant 0.975quant mode
## Precision parameter for the Gamma observations 13.122 1.50e+01 13.051
## Precision for seaDist 8073.163 3.44e+04 5222.609
## Theta1 for field -1.514 1.69e-01 -1.721
## Theta2 for field 1.156 1.73e+00 1.228
## Theta3 for field 0.655 1.98e+00 0.857
##
## Marginal log-Likelihood: -1261.13
## Posterior summaries for the linear predictor and the fitted values are computed
## (Posterior marginals needs also 'control.compute=list(return.marginals.predictor=TRUE)')
Also, we can have the summary in the user’s scale:
## mean sd 0.025quant 0.5quant 0.975quant mode
## tau 0.317372 0.312494 0.061420 0.220209 1.18531 0.126464
## kappa 3.277030 1.046510 1.568620 3.179830 5.64968 2.954770
## nu 1.261120 0.328951 0.522721 1.317100 1.75727 1.478810
and the plot of the posterior marginal densities
The inital values to be used by R-INLA
’s optimization algorithm can be changed by setting the arguments start.ltau
, start.lkappa
and start.nu
.
start.ltau
will be the initial value for \(\log(\tau)\), that is, the logarithm of \(\tau\).
start.lkappa
will be the inital value for \(\log(\kappa)\), that is, the logarithm of \(\kappa\).
start.nu
will be the initial value for \(\nu\). Notice that here the initial value is not on the log scale.
One can change the initial value of one or more parameters.
For instance, let us consider the example with precipitation data, rspde_order=3
, but change the initial values to the ones close to the fitted value when considering the default rspde_order
(which is 2):
rspde_model_order_3_start <- rspde.matern(mesh = prmesh, rspde_order = 3,
nu_upper_bound = 2,
start.lkappa = result_fit$summary.log.kappa$mean,
start.ltau = result_fit$summary.log.tau$mean,
start.nu = result_fit$summary.nu$mean)
Since we already computed the \(A\) matrix and the index for rspde_order=3
, all we have to do is to update the formula and fit:
f.s.3.start <- y ~ -1 + Intercept + f(seaDist, model = "rw1") +
f(field, model = rspde_model_order_3_start)
rspde_fit_order_3_start <- inla(f.s.3.start, family = "Gamma",
data = inla.stack.data(stk.dat.3),
verbose = FALSE,
control.inla=list(int.strategy='eb'),
control.predictor = list(A = inla.stack.A(stk.dat.3),
compute = TRUE),
inla.mode = "experimental")
We have the summary:
##
## 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 = 4.01, Running = 21.9, Post = 0.113, Total = 26
## Fixed effects:
## mean sd 0.025quant 0.5quant 0.975quant mode kld
## Intercept 0.486 0.014 0.459 0.486 0.513 0.486 0
##
## Random effects:
## Name Model
## seaDist RW1 model
## field RGeneric2
##
## Model hyperparameters:
## mean sd 0.025quant
## Precision parameter for the Gamma observations 13.184 0.901 11.482
## Precision for seaDist 10557.110 9552.586 2467.476
## Theta1 for field -1.483 0.641 -2.769
## Theta2 for field 1.191 0.274 0.653
## Theta3 for field 0.623 0.662 -0.662
## 0.5quant 0.975quant mode
## Precision parameter for the Gamma observations 13.161 15.029 13.124
## Precision for seaDist 7705.349 35708.228 4804.647
## Theta1 for field -1.476 -0.232 -1.450
## Theta2 for field 1.190 1.733 1.185
## Theta3 for field 0.616 1.941 0.589
##
## Marginal log-Likelihood: -1261.36
## Posterior summaries for the linear predictor and the fitted values are computed
## (Posterior marginals needs also 'control.compute=list(return.marginals.predictor=TRUE)')
The rspde.matern()
function has an optimized version, which is the default and a non-optimized version.
It will take less time to fit the optimized object than to fit the non-optimized one. However it requires a sparsity analysis when creating the model object. If the size of the data is small it might happen that the time to analyze the sparsity plus the time to fit the model is larger than the time to fit non-optimized model, which does not require a sparsity analysis when creating the model object.
There is also another option for the optimized model, which is the sharp
argument. The sharp
argument is only used if optimize
is TRUE
. If sharp=TRUE
the time to fit the model is less than the time to fit a model in which we have sharp=FALSE
. However, the sparsity analysis takes more time when sharp=TRUE
than when sharp=FALSE
.
Let us then create the three model objects with rspde_order=1
for the precipitation data and compare the times.
We begin by creating the \(A\) matrix, index and inla.stack
object that all the three models will use:
Abar.1 <- rspde.make.A(mesh = prmesh, loc = coords, rspde_order=1)
mesh.index.1 <- rspde.make.index(name = "field", mesh = prmesh, rspde_order=1)
stk.dat.1 <- inla.stack(
data = list(y = Y), A = list(Abar.1, 1), tag = "est",
effects = list(c(mesh.index.1,
list(Intercept = 1)),
list(long = inla.group(coords[, 1]),
lat = inla.group(coords[,2]),
seaDist = inla.group(seaDist))))
Now, let us create the three models and compute the time it took to create the model objects:
#Creating the optimized and sharp model
start_time <- Sys.time()
rspde_model_opt_sharp <- rspde.matern(mesh = prmesh,
rspde_order = 1)
end_time <- Sys.time()
time_opt_sharp <- end_time - start_time
#Creating the optimized non-sharp model
start_time <- Sys.time()
rspde_model_opt_notsharp <- rspde.matern(mesh = prmesh,
rspde_order = 1,
sharp=FALSE)
end_time <- Sys.time()
time_opt_notsharp <- end_time - start_time
#Creating the non-optimized model
start_time <- Sys.time()
rspde_model_nonopt <- rspde.matern(mesh = prmesh,
rspde_order = 1,
optimize = FALSE)
end_time <- Sys.time()
time_nonopt <- end_time - start_time
Now, let us fit the models:
f.s.opt.sharp <- y ~ -1 + Intercept + f(seaDist, model = "rw1") +
f(field, model = rspde_model_opt_sharp)
rspde_fit_opt_sharp <- inla(f.s.opt.sharp, family = "Gamma",
data = inla.stack.data(stk.dat.1),
verbose = FALSE,
control.inla=list(int.strategy='eb'),
control.predictor=list(A=inla.stack.A(stk.dat.1),
compute = FALSE),
inla.mode = "experimental")
f.s.opt.notsharp <- y ~ -1 + Intercept + f(seaDist, model = "rw1") +
f(field, model = rspde_model_opt_notsharp)
rspde_fit_opt_notsharp <- inla(f.s.opt.notsharp, family = "Gamma",
data = inla.stack.data(stk.dat.1),
verbose = FALSE,
control.inla=list(int.strategy='eb'),
control.predictor=list(A=inla.stack.A(stk.dat.1),
compute = FALSE),
inla.mode = "experimental")
f.s.nonopt <- y ~ -1 + Intercept + f(seaDist, model = "rw1") +
f(field, model = rspde_model_nonopt)
rspde_fit_nonopt <- inla(f.s.nonopt, family = "Gamma",
data = inla.stack.data(stk.dat.1),
verbose = FALSE,
control.inla=list(int.strategy='eb'),
control.predictor=list(A=inla.stack.A(stk.dat.1),
compute = FALSE),
inla.mode = "experimental")
Finally, let us compare the times:
`Time to create model` <- c(time_opt_sharp, time_opt_notsharp, time_nonopt)
`Time to fit the model` <- c(rspde_fit_opt_sharp$cpu.used[[2]],
rspde_fit_opt_notsharp$cpu.used[[2]],
rspde_fit_nonopt$cpu.used[[2]])
`Total time` <- `Time to create model` + `Time to fit the model`
Model <- c("Opt & sharp", "Opt & not sharp", "Non-opt")
result_table <- data.frame(Model, `Time to create model`,
`Time to fit the model`,
`Total time`)
result_table
## Model Time.to.create.model Time.to.fit.the.model Total.time
## 1 Opt & sharp 1.417536 secs 27.77802 29.19555 secs
## 2 Opt & not sharp 1.276458 secs 31.27152 32.54798 secs
## 3 Non-opt 0.992240 secs 52.07295 53.06519 secs
Therefore, we see that for this example one benefits from using the optimized version.
For this example we will simulate a data with replicates. We will use the same example considered in the Rational approximation with the rSPDE
package vignette (the only difference is the way the data is organized). We also refer the reader to this vignette for a description of the function matern.operators()
, along with its methods (for instance, the simulate()
method).
Let us consider a simple Gaussian linear model with 30 independent replicates of a latent spatial field \(x(\mathbf{s})\), observed at the same \(m\) locations, \(\{\mathbf{s}_1 , \ldots , \mathbf{s}_m \}\), for each replicate. For each \(i = 1,\ldots,m,\) we have
\[\begin{align} y_i &= x_1(\mathbf{s}_i)+\varepsilon_i,\\ \vdots &= \vdots\\ y_{i+29m} &= x_{30}(\mathbf{s}_i) + \varepsilon_{i+29m}, \end{align}\]
where \(\varepsilon_1,\ldots,\varepsilon_{30m}\) are iid normally distributed with mean 0 and standard deviation 0.1.
We use the basis function representation of \(x(\cdot)\) to define the \(A\) matrix linking the point locations to the mesh. We also need to account for the fact that we have 30 replicates at the same locations. To this end, the \(A\) matrix we need can be generated by inla.spde.make.A()
function. The reason being that we are sampling \(x(\cdot)\) directly and not the latent vector described in the introduction of the Rational approximation with the rSPDE
package vignette.
We begin by creating the mesh:
m = 100
loc_2d_mesh = matrix(runif(m*2),m,2)
mesh_2d = inla.mesh.2d(
loc=loc_2d_mesh,
cutoff=0.05,
offset=c(0.1,0.4),
max.edge=c(0.05,0.5) )
plot(mesh_2d, main="")
points(loc_2d_mesh[,1],loc_2d_mesh[,2])
We then compute the \(A\) matrix, which is needed for simulation, and connects the observation locations to the mesh:
n.rep = 30
A=inla.spde.make.A(
mesh=mesh_2d,
loc=loc_2d_mesh,
index=rep(1:m,times=n.rep),
repl=rep(1:n.rep,each=m))
Notice that for the simulated data, we should use the \(A\) matrix from inla.spde.make.A()
function.
We will now simulate a latent process with standard deviation \(\sigma=1\) and range \(0.1\). We will use \(\nu=0.5\) so that the model has an exponential covariance function. To this end we create a model object with the matern.operators()
function:
nu = 0.5
sigma = 1
range = 0.1
kappa = sqrt(8*nu)/range
tau = 1/(sqrt(4*pi)*kappa*sigma)
d=2
operator_information = matern.operators (mesh=mesh_2d,
nu=nu,
kappa=kappa,
sigma=sigma,
m=2)
More details on this function can be found at the Rational approximation with the rSPDE package vignette.
To simulate the latent process all we need to do is to use the simulate()
method on the operator_information
object. We then obtain the simulated data \(y\) by connecting with the \(A\) matrix and adding the gaussian noise.
set.seed(1)
u = simulate(operator_information, nsim=n.rep)
y = as.vector(A %*% as.vector(u)) +
rnorm(m*n.rep)*0.1
The first replicate of the simulated random field as well as the observation locations are shown in the following figure.
Let us then use the rational SPDE approach to fit the data.
We begin by creating the \(A\) matrix and index with replicates, and the inla.stack
object. It is important to notice that since we have replicates we should provide the index
and repl
arguments for rspde.make.A()
function, and also the argument n.repl
in rspde.make.index()
function. They behave identically as in their R-INLA
’s counterparts, namely, inla.spde.make.A()
and inla.make.index()
.
Abar.rep <- rspde.make.A(mesh = mesh_2d, loc = loc_2d_mesh, index=rep(1:m,times=n.rep),
repl=rep(1:n.rep,each=m) )
mesh.index.rep <- rspde.make.index(name = "field", mesh = mesh_2d,
n.repl=n.rep)
st.dat.rep=inla.stack(
data=list(y=y),
A=Abar.rep,
effects=mesh.index.rep)
We now create the model object.
Finally, we create the formula and fit. It is extremely important not to forget the replicate
argument when building the formula as inla()
function will not produce warning and might fit some meaningless model.
f.rep =
y ~ -1 + f(field, model=rspde_model.rep,
replicate=field.repl)
rspde_fit.rep =
inla(f.rep,
data=inla.stack.data(st.dat.rep),
family="gaussian",
control.predictor=
list(A=inla.stack.A(st.dat.rep)),
inla.mode = "experimental")
We can get the summary:
##
## 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 = 4.1, Running = 236, Post = 1.35, Total = 242
## Random effects:
## Name Model
## field RGeneric2
##
## Model hyperparameters:
## mean sd 0.025quant 0.5quant
## Precision for the Gaussian observations 93.24 5.282 83.94 92.79
## Theta1 for field -2.74 0.090 -2.92 -2.74
## Theta2 for field 3.10 0.028 3.04 3.10
## Theta3 for field -1.50 0.035 -1.57 -1.50
## 0.975quant mode
## Precision for the Gaussian observations 104.91 91.46
## Theta1 for field -2.54 -2.74
## Theta2 for field 3.16 3.10
## Theta3 for field -1.42 -1.50
##
## Marginal log-Likelihood: -1582.48
## Posterior summaries for the linear predictor and the fitted values are computed
## (Posterior marginals needs also 'control.compute=list(return.marginals.predictor=TRUE)')
and the summary in the user’s scale:
## mean sd 0.025quant 0.5quant 0.975quant mode
## tau 0.0646112 0.0058046 0.0540921 0.0644521 0.0770699 0.0638721
## kappa 22.2884000 0.6158760 21.1292000 22.3043000 23.5585000 22.2848000
## nu 0.7293630 0.0209235 0.6899420 0.7302550 0.7725430 0.7299460
result_df <- data.frame(parameter = c("tau","kappa","nu"),
true = c(tau, kappa, nu),
mean = c(result_fit_rep$summary.tau$mean,
result_fit_rep$summary.kappa$mean,
result_fit_rep$summary.nu$mean),
mode = c(result_fit_rep$summary.tau$mode,
result_fit_rep$summary.kappa$mode,
result_fit_rep$summary.nu$mode))
print(result_df)
## parameter true mean mode
## 1 tau 0.01410474 0.06461116 0.06387213
## 2 kappa 20.00000000 22.28837243 22.28480013
## 3 nu 0.50000000 0.72936322 0.72994645
Bolin, David, and Kristin Kirchner. 2020. “The Rational SPDE Approach for Gaussian Random Fields with General Smoothness.” Journal of Computational and Graphical Statistics 29 (2): 274–85.
Lindgren, Finn, Håvard Rue, and Johan Lindström. 2011. “An Explicit Link Between Gaussian Fields and Gaussian Markov Random Fields: The Stochastic Partial Differential Equation Approach.” Journal of the Royal Statistical Society. Series B. Statistical Methodology 73 (4): 423–98.