Here's a vectorized approach, which works for arrays of an arbitrary amount of dimensions. The idea of this solution is to extend the functionality of the return_index
method in np.unique
, and return an array of arrays, each containing the N-dimensional indices of unique values in a numpy array.
For a more compact solution, I've defined the following function along with some explanations throughout the different steps:
def ndix_unique(x):
"""
Returns an N-dimensional array of indices
of the unique values in x
----------
x: np.array
Array with arbitrary dimensions
Returns
-------
- 1D-array of sorted unique values
- Array of arrays. Each array contains the indices where a
given value in x is found
"""
x_flat = x.ravel()
ix_flat = np.argsort(x_flat)
u, ix_u = np.unique(x_flat[ix_flat], return_index=True)
ix_ndim = np.unravel_index(ix_flat, x.shape)
ix_ndim = np.c_[ix_ndim] if x.ndim > 1 else ix_flat
return u, np.split(ix_ndim, ix_u[1:])
Checking with the array from the question -
a = np.array([[1, 0, 1],[2, 2, 0]])
vals, ixs = ndix_unique(a)
print(vals)
array([0, 1, 2])
print(ixs)
[array([[0, 1],
[1, 2]]),
array([[0, 0],
[0, 2]]),
array([[1, 0],
[1, 1]])]
Lets try with this other case:
a = np.array([[1,1,4],[2,2,1],[3,3,1]])
vals, ixs = ndix_unique(a)
print(vals)
array([1, 2, 3, 4])
print(ixs)
array([array([[0, 0],
[0, 1],
[1, 2],
[2, 2]]),
array([[1, 0],
[1, 1]]),
array([[2, 0],
[2, 1]]),
array([[0, 2]])], dtype=object)
For a 1D array:
a = np.array([1,5,4,3,3])
vals, ixs = ndix_unique(a)
print(vals)
array([1, 3, 4, 5])
print(ixs)
array([array([0]), array([3, 4]), array([2]), array([1])], dtype=object)
Finally another example with a 3D ndarray:
a = np.array([[[1,1,2]],[[2,3,4]]])
vals, ixs = ndix_unique(a)
print(vals)
array([1, 2, 3, 4])
print(ixs)
array([array([[0, 0, 0],
[0, 0, 1]]),
array([[0, 0, 2],
[1, 0, 0]]),
array([[1, 0, 1]]),
array([[1, 0, 2]])], dtype=object)