61

Is there any way to get the indices of several elements in a NumPy array at once?

E.g.

import numpy as np
a = np.array([1, 2, 4])
b = np.array([1, 2, 3, 10, 4])

I would like to find the index of each element of a in b, namely: [0,1,4].

I find the solution I am using a bit verbose:

import numpy as np

a = np.array([1, 2, 4])
b = np.array([1, 2, 3, 10, 4])

c = np.zeros_like(a)
for i, aa in np.ndenumerate(a):
    c[i] = np.where(b == aa)[0]
    
print('c: {0}'.format(c))

Output:

c: [0 1 4]
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
Franck Dernoncourt
  • 77,520
  • 72
  • 342
  • 501
  • 2
    np.where(np.in1d(b,a)) returns (array([0, 1, 4], dtype=int64),). Based on last example at http://docs.scipy.org/doc/numpy/reference/generated/numpy.where.html. –  Aug 24 '15 at 20:43

5 Answers5

63

You could use in1d and nonzero (or where for that matter):

>>> np.in1d(b, a).nonzero()[0]
array([0, 1, 4])

This works fine for your example arrays, but in general the array of returned indices does not honour the order of the values in a. This may be a problem depending on what you want to do next.

In that case, a much better answer is the one @Jaime gives here, using searchsorted:

>>> sorter = np.argsort(b)
>>> sorter[np.searchsorted(b, a, sorter=sorter)]
array([0, 1, 4])

This returns the indices for values as they appear in a. For instance:

a = np.array([1, 2, 4])
b = np.array([4, 2, 3, 1])

>>> sorter = np.argsort(b)
>>> sorter[np.searchsorted(b, a, sorter=sorter)]
array([3, 1, 0]) # the other method would return [0, 1, 3]
Community
  • 1
  • 1
Alex Riley
  • 169,130
  • 45
  • 262
  • 238
  • 4
    The documentation now recommends using [`isin`](https://docs.scipy.org/doc/numpy/reference/generated/numpy.isin.html) instead of `in1d`. – Arcturus B Sep 11 '18 at 08:13
  • Thanks for the ordered version! @ArcturusB: where do you find this information about the recommendation of using isin instead of in1d? – Elendil Oct 10 '19 at 11:43
  • 3
    @Elendil: in the [documentation of `in1d`](https://docs.scipy.org/doc/numpy/reference/generated/numpy.in1d.html) : “We recommend using isin instead of in1d for new code.” – Arcturus B Oct 10 '19 at 11:49
  • @ArcturusB: Oh didn't see it. I'm stucked to old version ([1.11.0](https://github.com/numpy/numpy/blob/v1.13.0/numpy/lib/arraysetops.py#L385) and this was not yet the case). It seems to appear in the [1.13.0](https://github.com/numpy/numpy/blob/v1.13.0/numpy/lib/arraysetops.py#L385) release. Thanks. – Elendil Oct 16 '19 at 07:28
13

This is a simple one-liner using the numpy-indexed package (disclaimer: I am its author):

import numpy_indexed as npi
idx = npi.indices(b, a)

The implementation is fully vectorized, and it gives you control over the handling of missing values. Moreover, it works for nd-arrays as well (for instance, finding the indices of rows of a in b).

Eelco Hoogendoorn
  • 10,459
  • 1
  • 44
  • 42
5

For an order-agnostic solution, you can use np.flatnonzero with np.isin (v 1.13+).

import numpy as np

a = np.array([1, 2, 4])
b = np.array([1, 2, 3, 10, 4])

res = np.flatnonzero(np.isin(a, b))  # NumPy v1.13+
res = np.flatnonzero(np.in1d(a, b))  # earlier versions

# array([0, 1, 2], dtype=int64)
jpp
  • 159,742
  • 34
  • 281
  • 339
  • 1
    This works beautifully for what I needed. – Jason Hite Jan 06 '21 at 15:43
  • What a nice solution! However, for anyone who tumbles into this solution, please switch the position of the two arrays to get the expected result: ```np.flatnonzero(a=np.isin(element=b, test_elements=a)```. – Cuong Jun 29 '23 at 03:27
3

All of the solutions here recommend using a linear search. You can use np.argsort and np.searchsorted to speed things up dramatically for large arrays:

sorter = b.argsort()
i = sorter[np.searchsorted(b, a, sorter=sorter)]
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
1

There are a bunch of approaches for getting the index of multiple items at once mentioned in passing in answers to this related question: Is there a NumPy function to return the first index of something in an array?. The wide variety and creativity of the answers suggests there is no single best practice, so if your code above works and is easy to understand, I'd say keep it.

I personally found this approach to be both performant and easy to read: https://stackoverflow.com/a/23994923/3823857

Adapting it for your example:

import numpy as np

a = np.array([1, 2, 4])
b_list = [1, 2, 3, 10, 4]
b_array = np.array(b_list)

indices = [b_list.index(x) for x in a]
vals_at_indices = b_array[indices]

I personally like adding a little bit of error handling in case a value in a does not exist in b.

import numpy as np

a = np.array([1, 2, 4])
b_list = [1, 2, 3, 10, 4]
b_array = np.array(b_list)
b_set = set(b_list)

indices = [b_list.index(x) if x in b_set else np.nan for x in a]
vals_at_indices = b_array[indices]

For my use case, it's pretty fast, since it relies on parts of Python that are fast (list comprehensions, .index(), sets, numpy indexing). Would still love to see something that's a NumPy equivalent to VLOOKUP, or even a Pandas merge. But this seems to work for now.

ClimbsRocks
  • 994
  • 13
  • 15