"""
Spatial weights for spatial interaction including contiguity OD weights (ODW),
network based weights (netW), and distance-decay based vector weights (vecW).
"""
# ruff: noqa: N802, N803, N999
__author__ = "Taylor Oshan <tayoshan@gmail.com> "
from collections import OrderedDict
from scipy.sparse import kron
from .distance import DistanceBand
from .weights import WSP, W
[docs]
def ODW(Wo, Wd, transform="r", silence_warnings=True):
"""
Constructs an o*d by o*d origin-destination style spatial weight for o*d
flows using standard spatial weights on o origins and d destinations. Input
spatial weights must be binary or able to be sutiably transformed to binary.
Parameters
----------
Wo : W object for origin locations
o x o spatial weight object amongst o origins
Wd : W object for destination locations
d x d spatial weight object amongst d destinations
transform : Transformation for standardization of final OD spatial weight; default
is 'r' for row standardized
Returns
-------
ww : spatial contiguity W object for assocations between flows
o*d x o*d spatial weight object amongst o*d flows between o
origins and d destinations
Examples
--------
>>> import libpysal
>>> O = libpysal.weights.lat2W(2,2)
>>> D = libpysal.weights.lat2W(2,2)
>>> OD = libpysal.weights.ODW(O,D)
>>> OD.weights[0]
[0.25, 0.25, 0.25, 0.25]
>>> OD.neighbors[0]
[5, 6, 9, 10]
>>> OD.full()[0][0]
array([0. , 0. , 0. , 0. , 0. , 0.25, 0.25, 0. , 0. , 0.25, 0.25,
0. , 0. , 0. , 0. , 0. ])
"""
if Wo.transform != "b":
try:
Wo.tranform = "b"
except: # noqa: E722
raise AttributeError(
"Wo is not binary and cannot be transformed to "
"binary. Wo must be binary or suitably transformed to binary."
) from None
if Wd.transform != "b":
try:
Wd.tranform = "b"
except: # noqa: E722
raise AttributeError(
"Wd is not binary and cannot be transformed to "
"binary. Wd must be binary or suitably transformed to binary."
) from None
wo = Wo.sparse
wo.eliminate_zeros()
wd = Wd.sparse
wd.eliminate_zeros()
ww = kron(wo, wd, format="csr")
ww.eliminate_zeros()
ww = WSP(ww).to_W(silence_warnings=silence_warnings)
ww.transform = transform
return ww
[docs]
def netW(link_list, share="A", transform="r", **kwargs):
"""
Create a network-contiguity based weight object based on different nodal
relationships encoded in a network.
Parameters
----------
link_list : list
of tuples where each tuple is of the form (o,d) where o is an
origin id and d is a destination id
share : string
denoting how to define the nodal relationship used to determine
neighboring edges; defualt is 'A' for any shared nodes between two
network edges; options include: O a shared origin node; D a shared
destination node; OD; a shared origin or a shared destination node;
C a shared node that is the destination of the first edge and the
origin of the second edge - i.e., a directed chain is formed moving
from edge one to edge two.
transform : Transformation for standardization of final OD spatial weight; default
is 'r' for row standardized
**kwargs : keyword arguments
optional arguments for :class:`pysal.weights.W`
Returns
-------
net_w : nodal contiguity W object for networkd edges or flows
W Object representing the binary adjacency of the network edges
given a definition of nodal relationshilibpysal.weights.spintW.
Examples
--------
>>> import libpysal
>>> links = [('a','b'), ('a','c'), ('a','d'), ('c','d'), ('c', 'b'), ('c','a')]
>>> O = libpysal.weights.netW(links, share='O')
>>> O.neighbors[('a', 'b')]
[('a', 'c'), ('a', 'd')]
>>> OD = libpysal.weights.netW(links, share='OD')
>>> OD.neighbors[('a', 'b')]
[('a', 'c'), ('a', 'd'), ('c', 'b')]
>>> any_common = libpysal.weights.netW(links, share='A')
>>> any_common.neighbors[('a', 'b')]
[('a', 'c'), ('a', 'd'), ('c', 'b'), ('c', 'a')]
"""
neighbors = {}
neighbors = OrderedDict()
edges = link_list
for key in edges:
neighbors[key] = []
for neigh in edges:
if key == neigh:
continue
if share.upper() == "OD":
if key[0] == neigh[0] or key[1] == neigh[1]:
neighbors[key].append(neigh)
elif share.upper() == "O":
if key[0] == neigh[0]:
neighbors[key].append(neigh)
elif share.upper() == "D":
if key[1] == neigh[1]:
neighbors[key].append(neigh)
elif share.upper() == "C":
if key[1] == neigh[0]:
neighbors[key].append(neigh)
elif share.upper() == "A":
if (
key[0] == neigh[0]
or key[0] == neigh[1]
or key[1] == neigh[0]
or key[1] == neigh[1]
):
neighbors[key].append(neigh)
else:
raise AttributeError(
"Parameter 'share' must be 'O', 'D'," " 'OD', or 'C'"
)
net_w = W(neighbors, **kwargs)
net_w.tranform = transform
return net_w
[docs]
def vecW(
origin_x,
origin_y,
dest_x,
dest_y,
threshold,
p=2,
alpha=-1.0,
binary=True,
ids=None,
build_sp=False, # noqa: ARG001
**kwargs,
):
"""
Distance-based spatial weight for vectors that is computed using a
4-dimensional distance between the origin x,y-coordinates and the
destination x,y-coordinates
Parameters
----------
origin_x : list or array
of vector origin x-coordinates
origin_y : list or array
of vector origin y-coordinates
dest_x : list or array
of vector destination x-coordinates
dest_y : list or array
of vector destination y-coordinates
threshold : float
distance band
p : float
Minkowski p-norm distance metric parameter:
1<=p<=infinity
2: Euclidean distance
1: Manhattan distance
binary : boolean
If true w_{ij}=1 if d_{i,j}<=threshold, otherwise w_{i,j}=0
If false wij=dij^{alpha}
alpha : float
distance decay parameter for weight (default -1.0)
if alpha is positive the weights will not decline with
distance. If binary is True, alpha is ignored
ids : list
values to use for keys of the neighbors and weights dicts
build_sp : boolean
True to build sparse distance matrix and false to build dense
distance matrix; significant speed gains may be obtained
dending on the sparsity of the of distance_matrix and
threshold that is applied
**kwargs : keyword arguments
optional arguments for :class:`pysal.weights.W`
Returns
-------
w : DistanceBand W object that uses 4-dimenional distances between
vectors origin and destination coordinates.
Examples
--------
>>> import libpysal
>>> x1 = [5,6,3]
>>> y1 = [1,8,5]
>>> x2 = [2,4,9]
>>> y2 = [3,6,1]
>>> W1 = libpysal.weights.vecW(x1, y1, x2, y2, threshold=999)
>>> list(W1.neighbors[0])
[1, 2]
>>> W2 = libpysal.weights.vecW(x1, y2, x1, y2, threshold=8.5)
>>> list(W2.neighbors[0])
[1, 2]
"""
data = list(zip(origin_x, origin_y, dest_x, dest_y, strict=True))
w = DistanceBand(
data,
threshold=threshold,
p=p,
binary=binary,
alpha=alpha,
ids=ids,
build_sp=False,
**kwargs,
)
return w
[docs]
def mat2L(edge_matrix):
"""
Convert a matrix denoting network connectivity (edges or flows) to a list
denoting edges
Parameters
----------
edge_matrix : array
where rows denote network edge origins, columns denote
network edge destinations, and non-zero entries denote the
existence of an edge between a given origin and destination
Returns
-------
edge_list : list
of tuples where each tuple is of the form (o,d) where o is an
origin id and d is a destination id
"""
if len(edge_matrix.shape) != 2:
raise AttributeError(
"Matrix of network edges should be two dimensions"
"with edge origins on one axis and edge destinations on the"
"second axis with non-zero matrix entires denoting an edge"
"between and origin and destination"
)
edge_list = []
rows, cols = edge_matrix.shape
for row in range(rows):
for col in range(cols):
if edge_matrix[row, col] != 0:
edge_list.append((row, col))
return edge_list