Fast, reproducible Local Indicators of Spatial Association (LISA) with arbitrary spatial weights.
fastLISA computes seven families of LISA statistics with
a plain-C backend, optional OpenMP multi-threading, and a modern
xoshiro256++ random number generator for permutation-based
inference. It accepts any spdep
listw spatial weights object — including custom and
non-contiguity (e.g. distance-decay) weights — and returns compact,
inspectable, spdep-compatible matrices.
The two established R tools force a trade-off:
listw and integrates
with the R spatial ecosystem, but its conditional-permutation inference
runs largely in R and is slow on large maps.fastLISA closes the gap:
listw weights, so distance-decay and other non-binary
schemes are respected (unlike rgeoda).iseed the
pseudo-p-values are identical regardless of n.cores — a
guarantee neither spdep nor rgeoda offers.spdep-style classes and cluster/quadrant attributes, not
opaque pointers.| Function | Statistic |
|---|---|
local_moran() |
Univariate local Moran’s I |
local_moran_bv() |
Bivariate local Moran’s I |
local_moran_eb() |
Empirical-Bayes-rate local Moran’s I |
local_geary() |
Univariate local Geary’s C |
local_multigeary() |
Multivariate local Geary’s C |
local_g() |
Getis-Ord local G |
local_gstar() |
Getis-Ord local G* |
Each returns the observed statistic, a permutation z-score,
and a pseudo p-value (folded for Moran/G/G*, tail-adaptive for
Geary), with optional permutation-moment columns. Cluster codes follow
rgeoda conventions, including an Isolated
category for observations with no neighbours.
Install the released version from CRAN:
install.packages("fastLISA")Or the development version from source (requires a C99 compiler; OpenMP is used when available):
# install.packages("remotes")
remotes::install_github("lizhongc/fastLISA")Or from a local clone:
R CMD INSTALL fastLISAspdep is suggested for constructing listw
weights and for the examples.
library(spdep)
library(fastLISA)
nb <- cell2nb(7, 7) # 49 cells on a 7 x 7 grid
lw <- nb2listw(nb, style = "W") # row-standardised weights
x <- as.numeric(seq_len(49)) # a simple gradient
res <- local_moran(x, lw, nsim = 999L, iseed = 1L, n.cores = 1L)
head(res)
attr(res, "cluster") # High-High / Low-Low / outliers / Isolated ...Custom (e.g. distance-decay) weights are passed through unchanged:
coords <- as.matrix(expand.grid(x = 1:7, y = 1:7)) # 49 grid points
dnb <- dnearneigh(coords, 0, 2) # neighbours within distance 2
glist <- lapply(nbdists(dnb, coords), function(d) exp(-d)) # distance decay
lw_exp <- nb2listw(dnb, glist = glist, style = "B")
res_exp <- local_g(x, lw_exp, nsim = 999L, iseed = 1L)All functions share the same interface: nsim
permutations, an optional integer iseed for
reproducibility, a significance cutoff p.value,
n.cores (default 1L; raise it to use multiple
OpenMP threads), and p.method to choose the pseudo-p-value
method — "count" (default) or spdep’s
ties-averaged "rank".
Because the RNG is re-seeded per observation rather than per thread,
the same iseed yields bit-identical pseudo-p-values whether
you run on 1 core or many:
a <- local_moran(x, lw, nsim = 999L, iseed = 42L, n.cores = 1L)
b <- local_moran(x, lw, nsim = 999L, iseed = 42L, n.cores = 8L)
identical(c(a), c(b)) # TRUE -- same statistics and pseudo-p-values(c() strips attributes; the full objects differ only in
the recorded call, which stores the n.cores
value you passed.)
See the package help (?local_moran,
?local_g, ?local_geary, …) and the package
vignette:
vignette("fastLISA")GPL-3.