geovalidate.LocalPermutation

class geovalidate.LocalPermutation(bandwidth=None, k=None, kernel='uniform', derangement=True, n_permutations=99, n_burn=None, graph=None, random_state=None)[source]

Spatially-constrained permutation without replacement (derangement).

Shuffles the rows of an n-site dataset so that each row moves only within a local neighbourhood defined by bandwidth, k, or a pre-built graph. Every row appears exactly once – it is a true permutation, analogous to LocalBootstrap but without replacement.

Optionally enforced as a derangement: no row may remain at its original site.

Connectivity / proposal weighting

bandwidth + kernel (default 'uniform')

Kernel-weighted adjacency. With kernel='uniform' (the default) this is a hard distance cutoff – binary adjacency, proposals drawn uniformly over edges – which recovers the classic threshold behaviour. Smooth kernels (bisquare, gaussian, …) weight proposals so nearby pairs are proposed more often.

k + kernel

k-nearest-neighbour adjacency with an adaptive per-site bandwidth equal to the distance to the k-th neighbour. The adjacency is symmetrised (union of both directions) and proposals are weighted by the kernel values.

graph

Every directly-connected pair may swap; stored edge weights drive the proposal distribution.

Algorithm

  1. Build a weighted adjacency: feasible pairs plus proposal weights.

  2. Find an initial feasible permutation via scipy.sparse.csgraph.min_weight_full_bipartite_matching on a sparse cost matrix with i.i.d. Uniform[0, 1] weights on feasible pairs – a random feasible matching without a dense (n, n) array.

  3. Mix with a Markov chain: sample candidate pair (i, j) proportional to edge weight, propose swapping perm[i] <-> perm[j], accept iff both moves stay within the adjacency and neither creates a fixed point. Run n_burn steps between yields.

param bandwidth:

Neighbourhood radius in the same units as the input distances. Required unless k or graph is provided. Mutually exclusive with k.

type bandwidth:

float or None

param k:

Number of nearest neighbours. Mutually exclusive with bandwidth.

type k:

int or None

param kernel:

One of 'uniform', 'bisquare', 'triangular', 'gaussian', 'exponential', 'parabolic'. 'uniform' gives a hard cutoff (binary adjacency); smooth kernels weight swap proposals by proximity.

type kernel:

str, default ‘uniform’

param derangement:

If True, every value must move (no fixed points).

type derangement:

bool, default True

param n_permutations:

Number of permutations to generate.

type n_permutations:

int, default 99

param n_burn:

Proposed Markov-chain steps between each yielded permutation. Defaults to 10 * n.

type n_burn:

int or None

param graph:

Pre-built spatial weights (must expose .sparse). Edge weights drive proposals. Overrides bandwidth and k.

type graph:

libpysal.graph.Graph or None

param random_state:

type random_state:

int, RandomState instance, or None

raises ValueError:

If no valid (de)rangement exists for the given constraints, or if neither bandwidth, k, nor graph is supplied.

Examples

Bandwidth with default uniform kernel (hard cutoff):

>>> lp = LocalPermutation(bandwidth=50_000, random_state=0)
>>> for perm in lp.sample(gdf):
...     model.fit(X[perm], y[perm])

Bandwidth with a smooth kernel (nearby pairs proposed more often):

>>> lp = LocalPermutation(bandwidth=50_000, kernel='bisquare',
...                       random_state=0)
>>> for perm in lp.sample(gdf):
...     model.fit(X[perm], y[perm])
__init__(bandwidth=None, k=None, kernel='uniform', derangement=True, n_permutations=99, n_burn=None, graph=None, random_state=None)[source]

Methods

__init__([bandwidth, k, kernel, ...])

fit(X[, y])

Build the spatial adjacency structure.

get_metadata_routing()

Get metadata routing of this object.

get_params([deep])

Get parameters for this estimator.

sample(X)

Yield constrained permutation index arrays.

set_params(**params)

Set the parameters of this estimator.

fit(X, y=None)[source]

Build the spatial adjacency structure.

Parameters:
X : GeoDataFrame | GeoSeries | (n, 2) ndarray | (n,) or (n, 1) ndarray

Locations.

y : array-like or None

Response variable; required when bandwidth='auto' or k='auto'.

Return type:

self

get_metadata_routing()[source]

Get metadata routing of this object.

Please check User Guide on how the routing mechanism works.

Returns:

routing – A MetadataRequest encapsulating routing information.

Return type:

MetadataRequest

get_params(deep=True)[source]

Get parameters for this estimator.

Parameters:
deep : bool, default=True

If True, will return the parameters for this estimator and contained subobjects that are estimators.

Returns:

params – Parameter names mapped to their values.

Return type:

dict

sample(X)[source]

Yield constrained permutation index arrays.

Parameters:
X : GeoDataFrame | GeoSeries | (n, 2) ndarray | (n,) or (n, 1) ndarray

Locations. When graph is provided coordinates are used only to determine n.

Yields:

perm (ndarray of shape (n,)) – perm[i] is the label of the row assigned to position i, using the input’s index when X is a GeoDataFrame/GeoSeries, or integer positions for raw arrays.

set_params(**params)[source]

Set the parameters of this estimator.

The method works on simple estimators as well as on nested objects (such as Pipeline). The latter have parameters of the form <component>__<parameter> so that it’s possible to update each component of a nested object.

Parameters:
**params : dict

Estimator parameters.

Returns:

self – Estimator instance.

Return type:

estimator instance