# Niharika Reddy Peddinenikalva
# Vacation Scholarship project
# Updated for sf by Finn Lindgren 2024
suppressPackageStartupMessages(library("RColorBrewer"))
suppressPackageStartupMessages(library("ggplot2"))
suppressPackageStartupMessages(library("dplyr"))
suppressPackageStartupMessages(library("lwgeom"))
suppressPackageStartupMessages(library("patchwork"))
suppressPackageStartupMessages(library("terra"))
suppressPackageStartupMessages(library("sf"))
suppressPackageStartupMessages(library("INLA"))
suppressPackageStartupMessages(library("inlabru"))
theme_set(theme_bw())
#' ----------------------------------
#' prepare_residual_calculations
#' ----------------------------------
#'
#' Computes the A_sum, A_integrate matrices and the data frame used for
#' calculating the residuals for the given set of polygons B
#' Input:
#' @param samplers `sf` polygons containing partitions for which
#' residuals are to be calculated
#' @param domain A mesh object
#' @param observations `sf` points containing observed data
#'
#' Output:
#' @return A_sum - matrix used to compute the summation term of the residuals
#' @return A_integrate - matrix used to compute the integral term
#' @return df - `sf` containing all the locations 'u' for
#' calculating residuals
#'
prepare_residual_calculations <- function(samplers, domain, observations) {
# Calculate the integration weights for A_integrate
ips <- fm_int(domain = domain, samplers = samplers)
# Set-up the A_integrate matrix
# A_integrate has as many rows as polygons in the samplers,
# as many columns as mesh points
A_integrate <- inla.spde.make.A(
mesh = domain, ips, weights = ips$weight,
block = ips$.block, block.rescale = "none"
)
# Set-up the A_sum matrix
# A_sum has as many rows as polygons in the samplers,
# as many columns as observed points
# each row has 1s for the points in the corresponding polygon
idx <- sf::st_within(
observations,
samplers,
sparse = TRUE
)
A_sum <- sparseMatrix(
i = unlist(idx),
j = rep(
seq_len(nrow(observations)),
vapply(idx, length, 1L)
),
x = rep(1, length(unlist(idx))),
dims = c(nrow(samplers), nrow(observations))
)
# Setting up the data frame for calculating residuals
observations$obs <- TRUE
df <- st_as_sf(
data.frame(
obs = rep(FALSE, domain$n),
x = domain$loc[, 1],
y = domain$loc[, 2]
),
coords = c("x", "y"),
crs = fm_crs(domain)
)
df <- dplyr::bind_rows(df, observations)
# Return A-sum, A_integrate and the data frame for predicting the residuals
list(A_sum = A_sum, A_integrate = A_integrate, df = df)
}
#' ------------
#' residual_df
#' ------------
#'
#' Computes the three types if residuals and returns a data frame containing
#' information about all 3 residuals for each partition of 'B'
#'
#' Inputs:
#' @param model fitted model for which residuals need to be calculated
#' @param df sf object containing all the locations 'u'
#' for calculating residuals
#' @param expr an expression object containing the formula of the model
#' @param A_sum matrix used to compute the summation term of the residuals
#' @param A_integrate matrix that computes the integral term of the residuals
#'
#' Outputs:
#' @return Data frame containing residual information for each of the
#' partitions of the subset 'B'
#'
residual_df <- function(model, df, expr, A_sum, A_integrate) {
# Compute residuals
res <- predict(
object = model,
newdata = df,
~ {
lambda <- eval(expr)
h1 <- lambda * 0 + 1
h2 <- 1 / lambda
h3 <- 1 / sqrt(lambda)
data.frame(
Scaling_Residuals =
as.vector(A_sum %*% h1[obs]) -
as.vector(A_integrate %*% (h1 * lambda)[!obs]),
Inverse_Residuals =
as.vector(A_sum %*% h2[obs]) -
as.vector(A_integrate %*% (h2 * lambda)[!obs]),
Pearson_Residuals =
as.vector(A_sum %*% h3[obs]) -
as.vector(A_integrate %*% (h3 * lambda)[!obs])
)
},
used = bru_used(expr)
)
# Label the three types of residuals
res$Scaling_Residuals$Type <- "Scaling Residuals"
res$Inverse_Residuals$Type <- "Inverse Residuals"
res$Pearson_Residuals$Type <- "Pearson Residuals"
do.call(rbind, res)
}
#' --------------
#' set_csc
#' --------------
#'
#' Sets the colour scale for the three types of residuals
#'
#' Inputs:
#' @param residuals frame containing residual information for each of the
#' partitions of the subset 'B'
#' @param col_theme vector of themes for each type of residual
#'
#' Outputs:
#' @return a list of 3 colour scales for each type of residual
#'
set_csc <- function(residuals, col_theme) {
# Store data for the colour scale of the plots for each type of residual
cscrange <- data.frame(
residuals %>%
group_by(Type) %>%
summarise(maxabs = max(abs(mean)))
)
# Set the colour scale for all three types of residuals
scaling_csc <-
scale_fill_gradientn(
colours = brewer.pal(9, col_theme[1]),
name = "Scaling Residual",
limits =
cscrange[cscrange$Type == "Scaling Residuals", 2] *
c(-1, 1)
)
inverse_csc <-
scale_fill_gradientn(
colours = brewer.pal(9, col_theme[2]),
name = "Inverse Residual",
limits =
cscrange[cscrange$Type == "Inverse Residuals", 2] *
c(-1, 1)
)
pearson_csc <-
scale_fill_gradientn(
colours = brewer.pal(9, col_theme[3]),
name = "Pearson Residual",
limits =
cscrange[cscrange$Type == "Pearson Residuals", 2] *
c(-1, 1)
)
list("Scaling" = scaling_csc,
"Inverse" = inverse_csc,
"Pearson" = pearson_csc)
}
#' ---------------
#' residual_plot
#' ---------------
#'
#' plots the three types of residuals for each polygon
#'
#' Input:
#' @param samplers A sf containing partitions for which
#' residuals are to be calculated
#' @param residuals frame containing residual information for each of the
#' partitions of the subset 'B'
#' @param csc list of three colour scales for the three types of residuals
#' @param model_name string containing the name of the model being assessed
#'
#' Output:
#' @return a list of three subplots scaling, inverse and Pearson residuals
#' for the different partitions of samplers
residual_plot <- function(samplers, residuals, csc, model_name) {
# Initialise the scaling residuals plot
samplers$Residual <- residuals %>%
filter(Type == "Scaling Residuals") %>%
pull(mean)
scaling <- ggplot() +
gg(samplers, aes(fill = Residual), alpha = 1, colour = NA) +
csc["Scaling"] +
theme(legend.position = "bottom") +
labs(subtitle = paste(model_name, "Scaling"))
# Initialise the inverse residuals plot
samplers$Residual <- residuals %>%
filter(Type == "Inverse Residuals") %>%
pull(mean)
inverse <- ggplot() +
gg(samplers, aes(fill = Residual), alpha = 1, colour = NA) +
csc["Inverse"] +
theme(legend.position = "bottom") +
labs(subtitle = paste(model_name, "Inverse"))
# Initialise the Pearson residuals plot
samplers$Residual <- residuals %>%
filter(Type == "Pearson Residuals") %>%
pull(mean)
pearson <- ggplot() +
gg(samplers, aes(fill = Residual), alpha = 1, colour = NA) +
csc["Pearson"] +
theme(legend.position = "bottom") +
labs(subtitle = paste(model_name, "Pearson"))
# Return the three plots in a list
list(
Scaling = scaling, Inverse = inverse,
Pearson = pearson
)
}
#' ------------------
#' partition
#' ------------------
#'
#' Partitions the region based on the given criteria for calculating residuals
#' in each partition. Parts of this function are taken from concepts in
#' https://rpubs.com/huanfaChen/grid_from_polygon
#'
#' Input:
#' @param samplers A sf polygon containing region for which
#' partitions need to be created
#' @param resolution resolution of the grids that are required
#' @param nrows number of rows of grids that are required
#' @param ncols number of columns of grids that are required
#'
#' Output:
#' @return a partitioned sf with polygons as required
#'
#'
partition <- function(samplers, resolution = NULL, nrows = NULL, ncols = NULL) {
# Create a grid for the given boundary
if (is.null(resolution)) {
grid <- terra::rast(
terra::ext(samplers),
crs = fm_proj4string(samplers),
nrows = nrows, ncols = ncols
)
}
if (is.null(c(nrows, ncols))) {
grid <- terra::rast(
terra::ext(samplers),
crs = fm_proj4string(samplers),
resolution = resolution
)
}
gridPolygon <- terra::as.polygons(grid)
# Extract the boundary with subpolygons only
sf::st_as_sf(
terra::intersect(
gridPolygon,
terra::vect(samplers)
)
)
}
#' ------------------
#' edit_df
#' ------------------
#'
#' Edits the residual data frames to remove columns that need not be displayed
#'
#' Input:
#' @param df the data frame which needs to be edited
#' @param columns a vector of columns that need to be deleted from df
#'
#' Output:
#' @return the edited data frame with only the desired columns
#'
#'
edit_df <- function(df, columns) {
# Remove the columns that are not required
df[, !(colnames(df) %in% columns), drop = FALSE]
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.