The hardware and bandwidth for this mirror is donated by METANET, the Webhosting and Full Service-Cloud Provider.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]metanet.ch.
Many data sets come with known spatial relationships: pixels in an image, voxels in a brain scan, sensors on a grid, measurements at geographic locations. For these data the question “who is my neighbor?” has a physical answer — Euclidean distance in coordinate space — rather than a statistical one derived from feature vectors.
adjoin provides a focused set of functions that build
adjacency matrices directly from coordinate matrices. The output is
always a sparse Matrix with the same interface as
feature-based graphs: adjacency(),
laplacian(), and normalization all work the same way.
We will use a 6 × 6 grid throughout this vignette — small enough to visualize, representative of image and brain-imaging data.
spatial_adjacency() connects every point to its nearest
spatial neighbors. Two parameters shape the neighborhood:
sigma — the heat kernel bandwidth
(larger = broader neighborhood)nnk — the hard cap on neighbor count
(keeps the matrix sparse)A <- spatial_adjacency(coords, sigma = 1.5, nnk = 8,
weight_mode = "heat",
include_diagonal = FALSE,
normalized = TRUE)
cat("size:", nrow(A), "x", ncol(A),
"| non-zero entries:", Matrix::nnzero(A), "\n")
#> size: 36 x 36 | non-zero entries: 296
cat("symmetric:", isSymmetric(A), "\n")
#> symmetric: TRUEEach of the 36 grid points has up to 8 heat-kernel-weighted neighbors
within radius sigma × 3 = 4.5 grid units. Normalization
symmetrizes the matrix (\(D^{-1/2} A
D^{-1/2}\)) so that edge weights are comparable across nodes with
different numbers of neighbors.
Spatial neighbor graph on a jittered 6 × 6 grid. Thicker, darker lines are higher-weight edges (nearer neighbours); lighter lines are lower-weight (farther neighbours). Point colour encodes degree.
Corner and edge points have fewer neighbors than interior points. Because the coordinates are slightly irregular, edge weights also vary: thicker, darker lines connect closer pairs; faint lines connect pairs near the neighbourhood boundary.
weight_mode controls how spatial distance is converted
to an edge weight.
| Mode | Edge weight | Best for |
|---|---|---|
"heat" |
exp(−d²/2σ²) | Smooth, distance-proportional weights |
"binary" |
1 for every neighbor | Structural analysis, graph spectra |
A_heat <- spatial_adjacency(coords, sigma = 1.5, nnk = 8,
weight_mode = "heat",
include_diagonal = FALSE, normalized = FALSE,
stochastic = FALSE)
A_binary <- spatial_adjacency(coords, sigma = 1.5, nnk = 8,
weight_mode = "binary",
include_diagonal = FALSE, normalized = FALSE,
stochastic = FALSE)The heat kernel produces a smooth spectrum between 0 and 1; binary
produces a single spike at 1. Use "heat" when distance
matters, "binary" when only presence of a connection
matters.
Increasing sigma extends the neighborhood and softens
the weight decay, adding more edges. The nnk cap limits
runaway growth.
A_tight <- spatial_adjacency(coords, sigma = 0.8, nnk = 27,
weight_mode = "heat",
include_diagonal = FALSE, normalized = TRUE)
A_wide <- spatial_adjacency(coords, sigma = 2.5, nnk = 27,
weight_mode = "heat",
include_diagonal = FALSE, normalized = TRUE)
c(tight_edges = Matrix::nnzero(A_tight) / 2L,
wide_edges = Matrix::nnzero(A_wide) / 2L)
#> tight_edges wide_edges
#> 238 534spatial_smoother() converts a spatial adjacency into a
weighted averaging operator. Multiplying any signal vector by
S replaces each point’s value with a distance-weighted
average of its neighbors.
S <- spatial_smoother(coords, sigma = 1.5, nnk = 8, stochastic = FALSE)
set.seed(42)
signal_raw <- rnorm(36)
signal_smoothed <- as.numeric(S %*% signal_raw)Set stochastic = TRUE to apply Sinkhorn–Knopp
normalization and produce a doubly stochastic smoother (rows
and columns sum to 1).
spatial_laplacian() returns L = D − A, where D is the
degree matrix. Multiplying a signal by L measures its local curvature —
how much each point deviates from its neighbors.
L <- spatial_laplacian(coords, dthresh = 4.5, nnk = 8,
weight_mode = "binary", normalized = FALSE)
# Row sums of a Laplacian are zero by definition
range(round(rowSums(L), 10))
#> [1] 0 0Use normalized = TRUE for the symmetric form I −
D^{−1/2} A D^{−1/2}, which is standard for spectral clustering and graph
signal processing.
When you have both coordinates and feature observations,
weighted_spatial_adjacency() blends the two similarity
sources. The alpha parameter sweeps from pure spatial
weighting to pure feature weighting.
set.seed(42)
features <- matrix(rnorm(36 * 5), nrow = 36, ncol = 5) # random 5-d features
A_sp <- weighted_spatial_adjacency(coords, features,
alpha = 0, sigma = 2, nnk = 8)
A_mix <- weighted_spatial_adjacency(coords, features,
alpha = 0.5, sigma = 2, nnk = 8)
A_ft <- weighted_spatial_adjacency(coords, features,
alpha = 1, sigma = 2, nnk = 8)With random features the feature-only matrix is noisy. When features carry genuine spatial signal — say, image intensities — the blend rewards both proximity and similarity.
bilateral_smoother() is a spatial smoother that
down-weights neighbors with very different feature values. This is the
classic bilateral filter from image processing: it smooths within
regions but preserves sharp edges between them.
# Piecewise-constant signal with a hard boundary at x = 3.5
signal_field <- ifelse(coords[, "x"] <= 3, -1, 1) + rnorm(36, sd = 0.2)
feature_mat <- matrix(signal_field, ncol = 1)
B <- bilateral_smoother(coords, feature_mat,
nnk = 8, s_sigma = 1.5, f_sigma = 0.5)
signal_bilateral <- as.numeric(B %*% signal_field)
signal_spatial <- as.numeric(S %*% signal_field) # plain spatial smootherThe f_sigma parameter controls how sensitive the filter
is to feature differences: smaller values enforce stricter edge
preservation.
spatial_constraints() handles the case where the same
spatial layout appears across multiple blocks — for example,
the same brain or image grid measured across subjects or sessions. It
builds a combined constraint matrix encoding:
The shrinkage_factor balances these two: 0 = all
within-block, 1 = all between-block.
coords_small <- as.matrix(expand.grid(x = 1:4, y = 1:4)) # 16-point grid
# Pass a list with one entry per block (same grid repeated for two subjects)
S2 <- spatial_constraints(list(coords_small, coords_small), nblocks = 2,
sigma_within = 1.5, nnk_within = 6,
sigma_between = 2.0, nnk_between = 4,
shrinkage_factor = 0.15)
dim(S2) # 32 x 32: 2 blocks x 16 points
#> [1] 32 3232x32 constraint matrix for two 4x4 spatial blocks. Diagonal blocks capture within-block spatial similarity; off-diagonal blocks link corresponding locations across blocks. The leading eigenvalue is 1 by construction.
The result is normalized so its largest eigenvalue equals 1 — it plugs directly into spectral methods that expect a properly scaled operator.
For heterogeneous blocks where each block has different feature
observations, feature_weighted_spatial_constraints()
extends this with per-block feature weighting.
| Function | What it builds |
|---|---|
spatial_adjacency() |
Symmetric spatial similarity from one coordinate set |
cross_spatial_adjacency() |
Rectangular similarity between two coordinate sets |
spatial_laplacian() |
Graph Laplacian L = D − A from coordinates |
spatial_smoother() |
Row-stochastic spatial averaging operator |
weighted_spatial_adjacency() |
Spatial adjacency blended with feature similarity |
bilateral_smoother() |
Edge-preserving spatial smoother |
spatial_constraints() |
Multi-block constraint matrix for repeated layouts |
feature_weighted_spatial_constraints() |
Multi-block constraints with per-block feature weighting |
normalize_adjacency() |
Apply symmetric degree normalization |
make_doubly_stochastic() |
Sinkhorn–Knopp doubly stochastic normalization |
graph_weights()
and nnsearcher() build graphs from feature vectors rather
than coordinates; see vignette("adjoin").compute_diffusion_kernel()
propagates information through any adjacency matrix, spatial or
feature-based.expand_label_similarity() combines spatial proximity with
class labels for semi-supervised methods.?spatial_adjacency,
?spatial_constraints, ?bilateral_smoother for
full parameter documentation.These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.