Abstract
Task We briefly demonstrate the fitting of areal kernel models in Stan.
rstan_options(auto_write = TRUE)
options(mc.cores = parallel::detectCores())
sf <- mw
centroid_distance <- function(sf) {
cent <- sf::st_centroid(sf)
D <- sf::st_distance(cent, cent)
return(D)
}
D <- centroid_distance(sf)
## Warning in st_centroid.sf(sf): st_centroid assumes attributes are constant over geometries of x
dat <- list(n = nrow(sf),
y = sf$y,
m = sf$n_obs,
mu = rep(0, nrow(sf)),
D = D)
nsim_warm <- 100
nsim_iter <- 1000
fit_centroid <- rstan::stan(
"centroid.stan",
data = dat,
warmup = nsim_warm,
iter = nsim_iter
)
## Warning: Bulk Effective Samples Size (ESS) is too low, indicating posterior means and medians may be unreliable.
## Running the chains for more iterations may help. See
## https://mc-stan.org/misc/warnings.html#bulk-ess
## Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable.
## Running the chains for more iterations may help. See
## https://mc-stan.org/misc/warnings.html#tail-ess
sf <- mw
The number of integration points within each area:
L <- 10
The method for selecting integration points:
type <- "hexagonal"
The number of areas:
n <- nrow(sf)
Draw \(L\) samples from each area according to method type:
samples <- sf::st_sample(sf, type = type, size = rep(L, n))
plot_samples <- function(samples){
ggplot(sf) +
geom_sf(fill = "lightgrey") +
geom_sf(data = samples, alpha = 0.5, shape = 4) +
labs(x = "Longitude", y = "Latitude") +
theme_minimal() +
labs(fill = "") +
theme(axis.title = element_blank(),
axis.text = element_blank(),
axis.ticks = element_blank())
}
plot_samples(samples)
Construct an \(nL \times nL\) matrix
containing the Euclidean distance between each sample. Note that this
the \(L\) actually obtained will not be
exact for some settings of type (hexagonal
,
regular
):
S <- sf::st_distance(samples, samples)
dim(S)
## [1] 280 280
We use a start index data structure for unequal number of points in
each area. Note that other possible ways to do this include a padded
data structure
(matrix(unlist(padded_sample_index), nrow = n, ncol = max_length, byrow = TRUE)
)
and a database structure
group_db <- data.frame(i = unlist(sample_index), group_id = rep(seq(length(sample_index)), lengths(sample_index)))
).
sample_index <- sf::st_intersects(sf, samples)
sample_lengths <- lengths(sample_index)
start_index <- sapply(sample_index, function(x) x[1])
dat <- list(n = nrow(sf),
y = sf$y,
m = sf$n_obs,
mu = rep(0, nrow(sf)),
sample_lengths = sample_lengths,
total_samples = sum(sample_lengths),
start_index = start_index,
S = S)
fit_integrated <- rstan::stan(
"integrated.stan",
data = dat,
warmup = nsim_warm,
iter = nsim_iter
)
## Warning: Bulk Effective Samples Size (ESS) is too low, indicating posterior means and medians may be unreliable.
## Running the chains for more iterations may help. See
## https://mc-stan.org/misc/warnings.html#bulk-ess
## Warning: Tail Effective Samples Size (ESS) is too low, indicating posterior variances and tail quantiles may be unreliable.
## Running the chains for more iterations may help. See
## https://mc-stan.org/misc/warnings.html#tail-ess
samples_per_second <- function(fit) {
# Outside of warm-up phase
times <- rstan::get_elapsed_time(fit)
# Number of samples in first chain
nsim_iter <- fit@stan_args[[1]]$iter
nsim_iter / mean(times[, 2])
}
samples_per_second(fit_centroid)
## [1] 196.4958
samples_per_second(fit_integrated)
## [1] 3.540992
Trace-plots:
bayesplot::mcmc_trace(fit_centroid, pars = "l")
bayesplot::mcmc_trace(fit_centroid, pars = "beta_0")
bayesplot::mcmc_trace(fit_centroid, pars = "rho[28]") # For example
bayesplot::mcmc_trace(fit_integrated, pars = "l")
bayesplot::mcmc_trace(fit_integrated, pars = "beta_0")
bayesplot::mcmc_trace(fit_integrated, pars = "rho[28]")
Rank histograms:
bayesplot::mcmc_rank_overlay(fit_centroid, pars = "l")
bayesplot::mcmc_rank_overlay(fit_centroid, pars = "beta_0")
bayesplot::mcmc_rank_overlay(fit_centroid, pars = "rho[28]")
bayesplot::mcmc_rank_overlay(fit_integrated, pars = "l")
bayesplot::mcmc_rank_overlay(fit_integrated, pars = "beta_0")
bayesplot::mcmc_rank_overlay(fit_integrated, pars = "rho[28]")
All Rhat < 1.1 necessary but not sufficient:
which(rstan::summary(fit_centroid)[["summary"]][, "Rhat"] > 1.1)
## named integer(0)
which(rstan::summary(fit_integrated)[["summary"]][, "Rhat"] > 1.1)
## named integer(0)
Fitted values comparison:
fitted_centroid <- summary(fit_centroid)$summary[paste0("rho[", 1:28, "]"), "mean"]
fitted_integrated <- summary(fit_integrated)$summary[paste0("rho[", 1:28, "]"), "mean"]
plot(fitted_centroid, fitted_integrated)
centroid.stan
// centroid.stan
functions {
real xbinomial_logit_lpdf(real y, real m, real eta) {
real dens = lchoose(m, y) + y * log(inv_logit(eta)) + (m - y) * log(1 - inv_logit(eta));
real jacobian = log(inv_logit(eta)) + log(inv_logit(-eta)); // Alternative: eta - 2 * log(1 + exp(eta))
return dens + jacobian;
}
matrix cov_matern32(matrix D, real l) {
int n = rows(D);
matrix[n, n] K;
real norm_K;
real sqrt3;
sqrt3 = sqrt(3.0);
// Diagonal entries
for(i in 1:n){
K[i, i] = 1;
}
for(i in 1:(n - 1)){
for(j in (i + 1):n){
norm_K = D[i, j] / l;
K[i, j] = (1 + sqrt3 * norm_K) * exp(-sqrt3 * norm_K); // Fill lower triangle
K[j, i] = K[i, j]; // Fill upper triangle
}
}
return(K);
}
}
data {
int<lower=0> n; // Number of regions
vector[n] y; // Vector of observed responses
vector[n] m; // Vector of sample sizes
vector[n] mu; // Prior mean vector
matrix[n, n] D; // Distances between centroids
}
parameters {
real beta_0; // Intercept
vector[n] phi; // Spatial effects
real<lower=0> sigma_phi; // Standard deviation of spatial effects
real<lower=0> l; // Kernel lengthscale
}
transformed parameters {
vector[n] eta = beta_0 + sigma_phi * phi;
}
model {
matrix[n, n] K = cov_matern32(D, l);
// I could do this?
// matrix[n, n] L = cholesky_decompose(K);
// y ~ multi_normal_cholesky(mu, L);
l ~ gamma(1, 1);
sigma_phi ~ normal(0, 2.5); // Weakly informative prior
beta_0 ~ normal(-2, 1);
phi ~ multi_normal(mu, K);
for(i in 1:n) {
y[i] ~ xbinomial_logit(m[i], eta[i]);
}
}
generated quantities {
real tau_phi = 1 / sigma_phi^2; // Precision of spatial effects
vector[n] rho = inv_logit(beta_0 + sigma_phi * phi);
vector[n] log_lik;
for (i in 1:n) {
log_lik[i] = xbinomial_logit_lpdf(y[i] | m[i], eta[i]);
}
}
integrated.stan
// integrated.stan
functions {
real xbinomial_logit_lpdf(real y, real m, real eta) {
real dens = lchoose(m, y) + y * log(inv_logit(eta)) + (m - y) * log(1 - inv_logit(eta));
real jacobian = log(inv_logit(eta)) + log(inv_logit(-eta)); // Alternative: eta - 2 * log(1 + exp(eta))
return dens + jacobian;
}
matrix cov_matern32(matrix D, real l) {
int n = rows(D);
matrix[n, n] K;
real norm_K;
real sqrt3;
sqrt3 = sqrt(3.0);
for(i in 1:(n - 1)){
// Diagonal entries (apart from the last)
K[i, i] = 1;
for(j in (i + 1):n){
// Off-diagonal entries
norm_K = D[i, j] / l;
K[i, j] = (1 + sqrt3 * norm_K) * exp(-sqrt3 * norm_K); // Fill lower triangle
K[j, i] = K[i, j]; // Fill upper triangle
}
}
K[n, n] = 1;
return(K);
}
matrix cov_sample_average(matrix S, real l, int n, int[] start_index, int[] sample_lengths, int total_samples) {
matrix[n, n] K;
matrix[total_samples, total_samples] kS = cov_matern32(S, l);
for(i in 1:(n - 1)) {
// Diagonal entries (apart from the last)
K[i, i] = mean(block(kS, start_index[i], start_index[i], sample_lengths[i], sample_lengths[i]));
for(j in (i + 1):n) {
// Off-diagonal entries
K[i, j] = mean(block(kS, start_index[i], start_index[j], sample_lengths[i], sample_lengths[j]));
K[j, i] = K[i, j];
}
}
K[n, n] = mean(block(kS, start_index[n], start_index[n], sample_lengths[n], sample_lengths[n]));
return(K);
}
}
data {
int<lower=0> n; // Number of region
vector[n] y; // Vector of observed responses
vector[n] m; // Vector of sample sizes
vector[n] mu; // Prior mean vector
int sample_lengths[n]; // Number of Monte Carlo samples in each area
int<lower=0> total_samples; // sum(sample_lengths)
int start_index[n]; // Start indicies for each group of samples
matrix[total_samples, total_samples] S; // Distances between all points (could be sparser!)
}
parameters {
real beta_0; // Intercept
vector[n] phi; // Spatial effects
real<lower=0> sigma_phi; // Standard deviation of spatial effects
real<lower=0> l; // Kernel lengthscale
}
transformed parameters {
vector[n] eta = beta_0 + sigma_phi * phi;
}
model {
matrix[n, n] K = cov_sample_average(S, l, n, start_index, sample_lengths, total_samples);
// I could do this?
// matrix[n, n] L = cholesky_decompose(K);
// y ~ multi_normal_cholesky(mu, L);
l ~ gamma(1, 1);
sigma_phi ~ normal(0, 2.5); // Weakly informative prior
beta_0 ~ normal(-2, 1);
phi ~ multi_normal(mu, K);
for(i in 1:n) {
y[i] ~ xbinomial_logit(m[i], eta[i]);
}
}
generated quantities {
real tau_phi = 1 / sigma_phi^2; // Precision of spatial effects
vector[n] rho = inv_logit(beta_0 + sigma_phi * phi);
vector[n] log_lik;
for (i in 1:n) {
log_lik[i] = xbinomial_logit_lpdf(y[i] | m[i], eta[i]);
}
}
sessionInfo()
## R version 4.2.0 (2022-04-22)
## Platform: x86_64-apple-darwin17.0 (64-bit)
## Running under: macOS Monterey 12.6
##
## Matrix products: default
## LAPACK: /Library/Frameworks/R.framework/Versions/4.2/Resources/lib/libRlapack.dylib
##
## locale:
## [1] en_GB.UTF-8/en_GB.UTF-8/en_GB.UTF-8/C/en_GB.UTF-8/en_GB.UTF-8
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] tidyr_1.2.0 magrittr_2.0.3 dplyr_1.0.9 rmarkdown_2.14 bayesplot_1.9.0 rstan_2.21.5 ggplot2_3.3.6 StanHeaders_2.21.0-7
## [9] sf_1.0-7 bsae_0.2.7
##
## loaded via a namespace (and not attached):
## [1] viridis_0.6.2 sass_0.4.1 bit64_4.0.5 jsonlite_1.8.0 viridisLite_0.4.0 ids_1.0.1 bslib_0.3.1 RcppParallel_5.1.5
## [9] assertthat_0.2.1 distributional_0.3.0 posterior_1.2.2 askpass_1.1 highr_0.9 stats4_4.2.0 tensorA_0.36.2 blob_1.2.3
## [17] yaml_2.3.5 backports_1.4.1 pillar_1.7.0 RSQLite_2.2.14 lattice_0.20-45 glue_1.6.2 RcppEigen_0.3.3.9.2 uuid_1.1-0
## [25] digest_0.6.29 checkmate_2.1.0 colorspace_2.0-3 cowplot_1.1.1 htmltools_0.5.2 Matrix_1.5-1 plyr_1.8.7 pkgconfig_2.0.3
## [33] purrr_0.3.4 scales_1.2.0 processx_3.5.3 tibble_3.1.7 openssl_2.0.1 proxy_0.4-26 generics_0.1.2 farver_2.1.0
## [41] ellipsis_0.3.2 cachem_1.0.6 withr_2.5.0 cli_3.3.0 crayon_1.5.1 memoise_2.0.1 evaluate_0.15 ps_1.7.0
## [49] fansi_1.0.3 class_7.3-20 pkgbuild_1.3.1 tools_4.2.0 loo_2.5.1 prettyunits_1.1.1 lifecycle_1.0.1 matrixStats_0.62.0
## [57] stringr_1.4.0 munsell_0.5.0 callr_3.7.0 compiler_4.2.0 jquerylib_0.1.4 e1071_1.7-9 rlang_1.0.2 classInt_0.4-3
## [65] units_0.8-0 grid_4.2.0 ggridges_0.5.3 rstudioapi_0.13 labeling_0.4.2 orderly_1.4.3 gtable_0.3.0 codetools_0.2-18
## [73] abind_1.4-5 inline_0.3.19 DBI_1.1.2 reshape2_1.4.4 R6_2.5.1 gridExtra_2.3 knitr_1.39 bit_4.0.4
## [81] fastmap_1.1.0 utf8_1.2.2 rprojroot_2.0.3 BH_1.78.0-0 KernSmooth_2.23-20 stringi_1.7.6 parallel_4.2.0 Rcpp_1.0.8.3
## [89] vctrs_0.4.1 tidyselect_1.1.2 xfun_0.31