Skip to content

Commit b5dfdd2

Browse files
authored
Raise Exception on unrecognized sparse fmt (#26)
* ENH: add W.to_sparse W.from_sparse * Raise ValueError not Warning on bad sparse format
1 parent eae9831 commit b5dfdd2

File tree

2 files changed

+100
-1
lines changed

2 files changed

+100
-1
lines changed

libpysal/weights/tests/test_weights.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import tempfile
33

44
import unittest
5+
import pytest
56
from ..weights import W, WSP
67
from .. import util
78
from ..util import WSP2W, lat2W
@@ -10,6 +11,7 @@
1011
from ... import examples
1112
from ..distance import KNN
1213
import numpy as np
14+
import scipy.sparse
1315

1416
NPTA3E = np.testing.assert_array_almost_equal
1517

@@ -44,6 +46,7 @@ def setUp(self):
4446
}
4547

4648
self.w3x3 = util.lat2W(3, 3)
49+
self.w_islands = W({0: [1], 1: [0, 2], 2: [1], 3: []})
4750

4851
def test_W(self):
4952
w = W(self.neighbors, self.weights, silence_warnings=True)
@@ -355,6 +358,33 @@ def test_roundtrip_write(self):
355358
new = W.from_file(path)
356359
np.testing.assert_array_equal(self.w.sparse.toarray(), new.sparse.toarray())
357360

361+
def test_to_sparse(self):
362+
sparse = self.w_islands.to_sparse()
363+
np.testing.assert_array_equal(sparse.data, [1, 1, 1, 1, 0])
364+
np.testing.assert_array_equal(sparse.row, [0, 1, 1, 2, 3])
365+
np.testing.assert_array_equal(sparse.col, [1, 0, 2, 1, 3])
366+
sparse = self.w_islands.to_sparse("bsr")
367+
self.assertIsInstance(sparse, scipy.sparse._arrays.bsr_array)
368+
sparse = self.w_islands.to_sparse("csr")
369+
self.assertIsInstance(sparse, scipy.sparse._arrays.csr_array)
370+
sparse = self.w_islands.to_sparse("coo")
371+
self.assertIsInstance(sparse, scipy.sparse._arrays.coo_array)
372+
sparse = self.w_islands.to_sparse("csc")
373+
self.assertIsInstance(sparse, scipy.sparse._arrays.csc_array)
374+
sparse = self.w_islands.to_sparse()
375+
self.assertIsInstance(sparse, scipy.sparse._arrays.coo_array)
376+
377+
def test_sparse_fmt(self):
378+
with pytest.raises(ValueError) as exc_info:
379+
sparse = self.w_islands.to_sparse("dog")
380+
381+
def test_from_sparse(self):
382+
sparse = self.w_islands.to_sparse()
383+
w = W.from_sparse(sparse)
384+
self.assertEqual(w.n, 4)
385+
self.assertEqual(len(w.islands), 0)
386+
self.assertEqual(w.neighbors[3], [3])
387+
358388

359389
class Test_WSP_Back_To_W(unittest.TestCase):
360390
# Test to make sure we get back to the same W functionality

libpysal/weights/weights.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import numpy as np
1111
import scipy.sparse
1212
from scipy.sparse.csgraph import connected_components
13+
from sklearn import preprocessing
14+
from collections import defaultdict
1315

1416
# from .util import full, WSP2W resolve import cycle by
1517
# forcing these into methods
@@ -445,6 +447,73 @@ def sparse(self):
445447
self._cache["sparse"] = self._sparse
446448
return self._sparse
447449

450+
@classmethod
451+
def from_sparse(cls, sparse):
452+
"""Convert a ``scipy.sparse`` array to a PySAL ``W`` object.
453+
454+
Parameters
455+
----------
456+
sparse : scipy.sparse array
457+
458+
Returns
459+
-------
460+
w : libpysal.weights.W
461+
A ``W`` object containing the same graph as the ``scipy.sparse`` graph.
462+
463+
464+
Notes
465+
-----
466+
When the sparse array has a zero in its data attribute, and
467+
the corresponding row and column values are equal, the value
468+
for the pysal weight will be 0 for the "loop".
469+
"""
470+
coo = sparse.tocoo()
471+
neighbors = defaultdict(list)
472+
weights = defaultdict(list)
473+
for k, v, w in zip(coo.row, coo.col, coo.data):
474+
neighbors[k].append(v)
475+
weights[k].append(w)
476+
return W(neighbors=neighbors, weights=weights)
477+
478+
def to_sparse(self, fmt="coo"):
479+
"""Generate a ``scipy.sparse`` array object from a pysal W.
480+
481+
Parameters
482+
----------
483+
fmt : {'bsr', 'coo', 'csc', 'csr'}
484+
scipy.sparse format
485+
486+
Returns
487+
-------
488+
scipy.sparse array
489+
A scipy.sparse array with a format of fmt.
490+
491+
Notes
492+
-----
493+
The keys of the w.neighbors are encoded
494+
to determine row,col in the sparse array.
495+
496+
"""
497+
disp = {}
498+
disp["bsr"] = scipy.sparse.bsr_array
499+
disp["coo"] = scipy.sparse.coo_array
500+
disp["csc"] = scipy.sparse.csc_array
501+
disp["csr"] = scipy.sparse.csr_array
502+
fmt_l = fmt.lower()
503+
if fmt_l in disp:
504+
adj_list = self.to_adjlist(drop_islands=False)
505+
data = adj_list.weight
506+
row = adj_list.focal
507+
col = adj_list.neighbor
508+
le = preprocessing.LabelEncoder()
509+
le.fit(row)
510+
row = le.transform(row)
511+
col = le.transform(col)
512+
n = self.n
513+
return disp[fmt_l]((data, (row, col)), shape=(n, n))
514+
else:
515+
raise ValueError(f"unsupported sparse format: {fmt}")
516+
448517
@property
449518
def n_components(self):
450519
"""Store whether the adjacency matrix is fully connected."""
@@ -1366,7 +1435,7 @@ def plot(
13661435
ax.scatter(
13671436
gdf.centroid.apply(lambda p: p.x),
13681437
gdf.centroid.apply(lambda p: p.y),
1369-
**node_kws
1438+
**node_kws,
13701439
)
13711440
return f, ax
13721441

0 commit comments

Comments
 (0)