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 fastLISA
spdep 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 rule — the standard "count" (default), or spdep's
ties-averaged "rank", which differ only under exact ties.
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.