--- title: "Anisotropic models" author: "David Bolin and Alexandre B. Simas" date: "Created: 2024-10-23. Last modified: `r Sys.Date()`." output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Anisotropic models} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) library(rSPDE) library(INLA) set.seed(123) ``` ```{r inla_link, include = FALSE} inla_link <- function() { sprintf("[%s](%s)", "`R-INLA`", "https://www.r-inla.org") } ``` ## Introduction For domains $D\subset \mathbb{R}^2$, the `rSPDE` package implements the anisotropic Matérn model $$ (I - \nabla\cdot (H\nabla))^{(\nu + 1)/2} u = c\sigma W, \quad \text{on } D $$ Where $H$ is a $2\times 2$ positive definite matrix, $\sigma, \nu >0$ and $c$ is a constant chosen such that $u$ would have the covariance function $$ r(h) = \frac{\sigma^2}{2^{\nu-1}\Gamma(\nu)} (\sqrt{h^T H^{-1} h})^{\nu} K_{\nu}(\sqrt{h^T H^{-1} h}), $$ if the domain was $D = \mathbb{R}^2$, i.e., a stationary and anisotropic Matérn covariance function. The matrix $H$ is defined as $$ H = \begin{bmatrix} h_x^2 & h_xh_y h_{xy}\\ h_xh_y h_{xy} & h_y^2 \end{bmatrix} $$ with $h_x,h_y>0$ and $h_{xy} \in (-1,1)$. ## Implementation details Let us begin by loading some packages needed for making the plots ```{r library_loading, message=FALSE} library(ggplot2) library(gridExtra) library(viridis) ``` We start by creating a region of interest and a spatial mesh using the `fmesher` package: ```{r} library(fmesher) n_loc <- 2000 loc_2d_mesh <- matrix(runif(n_loc * 2), n_loc, 2) mesh_2d <- fm_mesh_2d( loc = loc_2d_mesh, cutoff = 0.01, max.edge = c(0.1, 0.5) ) plot(mesh_2d, main = "") ``` We now specify the model using the `matern2d.operators` function. ```{r} sigma <- 1 nu <- 0.5 hx <- 0.08 hy <- 0.08 hxy <- 0.5 op <- matern2d.operators( hx = hx, hy = hy, hxy = hxy, nu = nu, sigma = sigma, mesh = mesh_2d ) ``` The `matern2d.operators` object has `cov_function_mesh` method which can be used evaluate the covariance function on the mesh. For example ```{r} r <- cov_function_mesh(op, p = matrix(c(0.5, 0.5), 1, 2)) proj <- fm_evaluator(mesh_2d, dims = c(100, 100), xlim = c(0, 1), ylim = c(0, 1)) r.mesh <- fm_evaluate(proj, field = as.vector(r)) cov.df <- data.frame( x1 = proj$lattice$loc[, 1], x2 = proj$lattice$loc[, 2], cov = c(r.mesh) ) ggplot(cov.df, aes(x = x1, y = x2, fill = cov)) + geom_raster() + xlim(0, 1) + ylim(0, 1) + scale_fill_viridis() ``` We can simulate from the field using the `simulate method`: ```{r} u <- simulate(op) proj <- fm_evaluator(mesh_2d, dims = c(100, 100), xlim = c(0, 1), ylim = c(0, 1)) u.mesh <- fm_evaluate(proj, field = as.vector(u)) cov.df <- data.frame( x1 = proj$lattice$loc[, 1], x2 = proj$lattice$loc[, 2], u = c(u.mesh) ) ggplot(cov.df, aes(x = x1, y = x2, fill = u)) + geom_raster() + xlim(0, 1) + ylim(0, 1) + scale_fill_viridis() ``` Let us now simulate some data based on this simulated field. ```{r} n.obs <- 2000 obs.loc <- cbind(runif(n.obs), runif(n.obs)) A <- fm_basis(mesh_2d, obs.loc) sigma.e <- 0.1 Y <- as.vector(A %*% u + sigma.e * rnorm(n.obs)) ``` We can compute kriging predictions of the process $u$ based on these observations. To specify which locations that should be predicted, the argument `Aprd` is used. This argument should be an observation matrix that links the mesh locations to the prediction locations. ```{r, fig.show='hold',fig.align = "center"} A <- make_A(op, loc = obs.loc) Aprd <- make_A(op, loc = proj$lattice$loc) u.krig <- predict(op, A = A, Aprd = Aprd, Y = Y, sigma.e = sigma.e) ``` The process simulation, and the kriging prediction are shown in the following figure. ```{r, fig.show='hold', fig.align = "center",echo=TRUE} data.df <- data.frame(x = obs.loc[, 1], y = obs.loc[, 2], field = Y, type = "Data") krig.df <- data.frame( x = proj$lattice$loc[, 1], y = proj$lattice$loc[, 2], field = as.vector(u.krig$mean), type = "Prediction" ) df_plot <- rbind(data.df, krig.df) ggplot(df_plot) + aes(x = x, y = y, fill = field) + facet_wrap(~type) + geom_raster(data = krig.df) + geom_point( data = data.df, aes(colour = field), show.legend = FALSE ) + scale_fill_viridis() + scale_colour_viridis() ``` Let us finally use `rspde_lme` to estimate the parameters based on the data. ```{r} df <- data.frame(Y = as.matrix(Y), x = obs.loc[, 1], y = obs.loc[, 2]) res <- rspde_lme(Y ~ 1, loc = c("x", "y"), data = df, model = op, parallel = TRUE) ``` In the call, `y~1` indicates that we also want to estimate a mean value of the model, and the arguments `loc` provides the names of the spatial and temporal coordinates in the data frame. Let us see a summary of the fitted model: ```{r} summary(res) ``` Let us compare the estimated results with the true values: ```{r} results <- data.frame( sigma = c(sigma, res$coeff$random_effects[2]), hx = c(hx, res$coeff$random_effects[3]), hy = c(hy, res$coeff$random_effects[4]), hxy = c(hxy, res$coeff$random_effects[5]), nu = c(nu, res$coeff$random_effects[1]), sigma.e = c(sigma.e, res$coeff$measurement_error), intercept = c(0, res$coeff$fixed_effects), row.names = c("True", "Estimate") ) print(results) ``` Finally, we can also do prediction based on the fitted model as ```{r} pred.data <- data.frame(x = proj$lattice$loc[, 1], y = proj$lattice$loc[, 2]) pred <- predict(res, newdata = pred.data, loc = c("x", "y")) data.df <- data.frame(x = obs.loc[, 1], y = obs.loc[, 2], field = Y, type = "Data") krig.df <- data.frame( x = proj$lattice$loc[, 1], y = proj$lattice$loc[, 2], field = as.vector(u.krig$mean), type = "Prediction" ) df_plot <- rbind(data.df, krig.df) ggplot(df_plot) + aes(x = x, y = y, fill = field) + facet_wrap(~type) + geom_raster(data = krig.df) + geom_point( data = data.df, aes(colour = field), show.legend = FALSE ) + scale_fill_viridis() + scale_colour_viridis() ``` ## Inlabru Implementation We will now fit the model using the `inlabru` package. First, we load the package, and create the anisotropic model object: ```{r} library(inlabru) model_aniso <- rspde.anistropic2d(mesh = mesh_2d) ``` Then we create the component and fit the model using the `inlabru` package. Observe that we can use the same data frame as in the `rspde_lme` example. ```{r} cmp <- Y ~ Intercept(1) - 1 + field(cbind(x, y), model = model_aniso) bru_fit_field <- bru(cmp, data = df, options = list(num.threads = "1:1")) ``` Let us now compare the estimated results (the means of the parameters) with the true values. Observe that we are using the helper function `transform_parameters_anisotropic()` to change the scale of the estimated parameters back to the original scale. ```{r} results <- data.frame( rbind( # True values c(hx = hx, hy = hy, hxy = hxy, sigma = sigma, nu = nu), # Estimated values transformed using transform_parameters_anisotropic transform_parameters_anisotropic( bru_fit_field$summary.hyperpar$mean[2:6], model_aniso[["nu_upper_bound"]] ) ), # Add sigma.e values sigma.e = c(sigma.e, sqrt(1 / bru_fit_field$summary.hyperpar$mean[1])), # Add intercept values intercept = c(0, bru_fit_field$summary.fixed$mean), row.names = c("True", "Estimate") ) print(results) ```