Nonparametric tests of spatial independence on neighbourhood graphs, version 1.0.0.
Companion software to E. Ashtari Nezhad, A nonparametric test of spatial independence on neighbourhood graphs.
Install: pip install ivspatial · Source: github.com/emaad7/ivspatial · R version: github.com/emaad7/ivspatial-r
ivspatial tests whether the values of a variable observed at spatial units — cells of a grid,
polygons such as counties, irregularly located points, or nodes of a network — are independent and identically
distributed (i.i.d.) across the units. The tests are rank based and exact: under independence the P-value is
valid for every sample size, every graph and every continuous distribution, with no weight matrix, bandwidth or model
to choose. The package reports
spatial_test(x, graph) in Python, or ivspatial data.csv ... in a terminal.| Task | Window | Python | Command line |
|---|---|---|---|
| Build the graph | Neighbourhood graph box, Build graph | knn_graph, delaunay_graph, lattice_graph, polygon_contiguity_graph, graph_from_edges | --graph knn --x X --y Y --k 4 |
| Test one variable | choose Variable, Run test | spatial_test(x, g) | --column NAME |
| Screen many variables | tick test all | screen(dict, g) | --all |
| Residuals of a regression | tick test OLS residuals, select regressors | residual_test(y, X, g) | --residuals-on A,B |
| Data-driven resolution, lags | Resolution auto, Lags 2 | resolution="auto", lags=2 | --resolution auto --lags 2 |
| Maps | Map | local_features(x, g) | --local local.csv |
| Null distribution | Null distribution tab | null_limit(g, d, s) | ivspatial null ... |
You need Python 3.8 or later (https://www.python.org/downloads/; on Windows tick "Add python.exe to
PATH"). Then, in a terminal (Command Prompt on Windows):
pip install ivspatial # from the Python Package Index pip install "ivspatial[all]" # with Excel, shapefile and map support pip install git+https://github.com/emaad7/ivspatial # latest version from GitHub
numpy and scipy are installed automatically. Optional extras: pandas and
openpyxl for Excel files, pyshp for shapefiles (GeoJSON needs nothing), matplotlib
for maps. After installation you have the module import ivspatial, the command ivspatial,
and the command ivspatial-gui, which opens the window (also python -m ivspatial). Check with
ivspatial --version. On Linux the window may need sudo apt install python3-tk.
Without installing: download the ZIP from GitHub, unzip, run pip install numpy scipy once, and
double-click run_gui_windows.bat (Windows), run_gui_mac_linux.command (macOS, Linux) or
ivspatial_gui.pyw.
.shp with its .dbf and .shx) or a GeoJSON file;
the numeric attributes are the variables.Examples: mercer_hall_wheat.csv (grain and straw yields of 500 plots on a 20 × 25 grid; columns
row, col) and baltimore_houses.csv (211 house sales with coordinates
X, Y, price and characteristics) in the folder examples.
The neighbourhood of a site is the site and its neighbours. The graph is part of the hypothesis being tested: it says which sites are considered close. Common choices:
| Data | Graph | Typical choice |
|---|---|---|
| points | k nearest neighbours | k = 4 to 6; each site has the same number of neighbours |
| points | Delaunay triangulation | the contiguity of the Voronoi cells of the points; about 6 neighbours |
| points | distance band | all sites within a distance; check that few sites are isolated |
| grid | rook or queen contiguity | rook (4 neighbours) unless diagonal neighbours matter |
| polygons | queen (shared vertex) or rook (shared edge) | queen |
| network | edge list | the links of the network |
The report states the number of sites and the mean, minimum and maximum number of neighbours; isolated sites contribute nothing. For dependence that reaches beyond immediate neighbours use lags (Section 10) rather than very large neighbourhoods.
Start it with ivspatial-gui (optionally followed by a file name), python -m ivspatial, or by
double-clicking as described above. The Test tab:
The Null distribution tab gives the weighted chi-square limit of T_V(d, s) on the current graph: its mean, variance, critical values and the P-value of any value.
import numpy as np
import ivspatial as iv
w = iv.read_table("examples/mercer_hall_wheat.csv")
g = iv.lattice_from_indices(w["row"], w["col"], "rook")
res = iv.spatial_test(w["grain"], g, seed=1)
print(res) # the report below
res.p_value, res.reject # P-value of C4 and the decision
res.components # statistics and P-values of the four components
res.features # standardized features
res.to_dict() # everything, for saving as JSON
tab = iv.read_table("examples/baltimore_houses.csv")
g = iv.knn_graph(np.c_[tab["X"], tab["Y"]], k=4) # or iv.delaunay_graph(...)
print(iv.spatial_test(np.log(tab["PRICE"]), g, seed=1))
polys, att = iv.read_polygons("counties.shp") # or a .geojson file
gq = iv.polygon_contiguity_graph(polys, "queen")
results, adjusted = iv.screen({"HR80": att["HR80"], "HR90": att["HR90"]}, gq, seed=1)
Command line (one line per call):
ivspatial examples/mercer_hall_wheat.csv --graph lattice --row row --col col --column grain --seed 1 ivspatial examples/baltimore_houses.csv --x X --y Y --graph knn --k 4 --column PRICE --transform log --seed 1 ivspatial counties.shp --column HR90 --residuals-on RD90,PS90,UE90,DV90,MA90,SOUTH --seed 1 ivspatial data.csv --x x --y y --graph delaunay --all --out report.txt --json results.json ivspatial data.csv --x x --y y --column y --resolution auto --lags 2 --local local.csv ivspatial null data.csv --x x --y y --graph delaunay --d 4 --s 2 --value 35
lattice (20 x 25 cells), rook contiguity: n = 500 sites, degree mean 3.82 (min 2, max 4) Nonparametric test of spatial independence [grain] ==================================================== Observations n = 500 Neighbourhood graph: lattice (20 x 25 cells), rook contiguity: n = 500 sites, degree mean 3.82 (min 2, max 4) Random rank arrangements B = 9999 Test Statistic P-value asympt. C4 = min-P(V43, V42, RM, RS) [proposed] 0.0001 T_V(4,3): 43 features of order 2..3, 4 classes 586.104 0.0001 < 0.0001 T_V(4,2): 15 features of order 2, 4 classes 455.889 0.0001 < 0.0001 RM: rank Moran (level) 0.406 0.0001 RS: Moran of squared ranks (scale) 0.156 0.0001 Spatial independence is REJECTED at the 0.05 level (C4 = min-P(V43, V42, RM, RS)). Standardized features of T_V(3,2) (approximately N(0,1) under independence): n.lin^2 8.21 level: neighbours similar to each other n.lin*quad -2.15 mixed level-scale between neighbours n.quad^2 -0.42 scale: neighbours jointly extreme c.lin|n.lin 10.93 level: site and neighbours similar c.lin|n.quad -1.16 low/high site among extreme neighbours c.quad|n.lin -1.16 extreme site among low/high neighbours c.quad|n.quad 0.58 scale: extremes next to extremes |value| > 3 is notable; the sign of mixed features depends on the coding direction.
c.lin|n.lin is about 11 and n.lin^2 about 8: plots resemble their neighbours in level, while the
scale features are small. The sign of the mixed features (c.lin|n.quad, ...) changes if the variable is
recoded in the opposite direction; the other features do not.Every feature is a scaled sum of local contributions, one per site, each with mean 0 and variance 1 under independence. Mapping them shows where a feature receives its contribution, much as local indicators of spatial association decompose Moran's I. Red marks sites that agree with their neighbours in level (level feature) or that are extreme together with their neighbours (scale feature). The maps are descriptive: the local values are dependent and are not tested one by one.
Resolution. C4 uses four classes and orders two and three, a choice that is close to the best against
level and higher-order dependence. With resolution = "auto" the data choose among T_V(d, s), d = 2 to 6,
s = 2, 3, by the minimum-P rule; the selection is repeated in every random rank arrangement, so the P-value is exact
after selection, and the report states the resolution chosen. "schwarz" uses a penalized choice that
prefers simple statistics; it is more powerful when the dependence is low-dimensional and weaker against dependence
in the tails.
Lags. With lags = 2 the components are also computed for the neighbours at graph distance two and
all are combined by the minimum-P rule; use it when dependence may reach beyond immediate neighbours.
residual_test(y, X, g) tests the OLS residuals of y on the columns of X (an
intercept is added). With exogenous regressors — including coordinates, trend surfaces and fixed effects —
the tests remain valid without any correction. They are not valid for spatial-lag models, in which y is regressed on
its spatially lagged values; use a model-based bootstrap there.
Ashtari Nezhad, E. (2026). A nonparametric test of spatial independence on neighbourhood graphs.
Manuscript.
Ashtari Nezhad, E. (2026). ivspatial: nonparametric tests of spatial independence. Python package
version 1.0.0. https://github.com/emaad7/ivspatial