I have a bi-dimensional NumPy array of shape M × N with many values set to 0 and others with value ≠ 0.
The following is an example of the aforesaid matrix:
A = np.array([[0, 0, 0, 1, 0, 2, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 3, 0], [1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 6, 0, 0, 0], [0, 0, 1, 0, 0, 0, 0, 0]])
And that's it nicely formatted:
A = [[0 0 0 1 0 2 0 0]
[0 0 0 0 0 0 0 0]
[0 0 0 0 1 0 0 0]
[0 1 0 0 0 0 0 0]
[0 0 0 1 0 0 3 0]
[1 0 0 0 0 0 0 0]
[0 0 0 0 6 0 0 0]
[0 0 1 0 0 0 0 0]]
My task is to find, for each nonzero element (e.g. 1, 2, 1, 1, 1, 3, 1, 6 and 1) in the 2D array (A), the distance to the nearest nonzero element (except itself) by means of Euclidean distance, and then create a list (L) with the calculated distances.
The following invariant must hold:
if np.count_nonzero(A) < 2:
assert len(L) == 0
else
assert np.count_nonzero(A) == len(L)
Calculations for array A is the following:
- Nearest nonzero element for A[0, 3] = 1 is A[0, 5] = 2 at distance = 2
- Nearest nonzero element for A[0, 5] = 2 is A[0, 3] = 1 at distance = 2
- Nearest nonzero element for A[2, 4] = 1 is A[0, 3] = 1 at distance = 2.24
- Nearest nonzero element for A[3, 1] = 1 is A[4, 3] = 1 at distance = 2.24
- Nearest nonzero element for A[4, 3] = 1 is A[2, 4] = 1 at distance = 2.24
- Nearest nonzero element for A[4, 6] = 3 is A[2, 4] = 1 at distance = 2.83
- Nearest nonzero element for A[5, 0] = 1 is A[3, 1] = 1 at distance = 2.24
- Nearest nonzero element for A[6, 4] = 6 is A[4, 3] = 1 at distance = 2.24
- Nearest nonzero element for A[7, 2] = 1 is A[6, 4] = 6 at distance = 2.24
The list L is then L = [2, 2, 2.24, 2.24, 2.24, 2.83, 2.24, 2.24, 2.24].
I wrote the following code to solve the problem, and I think it works correctly, but it has two problems: it's the naive, brute force and non vectorized solution of (M² × N²) time complexity, and is not very clear, concise and succinct; that is, it's not Pythonic.
def get_distance_list(A):
L = []
for (m, n), a_mn in np.ndenumerate(A):
# skip this element if its value is 0
if a_mn == 0:
continue
d_min = math.inf
for (k, l), a_kl in np.ndenumerate(A):
# skip this element if its value is 0 or if it's me
if a_kl == 0 or (m, n) == (k, l):
continue
d = scipy.spatial.distance.euclidean((m, n), (k, l))
d_min = min(d_min, d)
# in case there are less than two nonzero values in the matrix,
# the returned list must be empty, so only add the distance
# if it's different than the default value of +inf
if d_min != math.inf:
L.append(d_min)
return L
Do you know if there is a built-in function (maybe in NumPy, SciPy, SciKit, etc.) which can replace the one I wrote, or if there is a faster/vectorized and more Pythonic way to solve the problem?