Есть ли функция NumPy для возврата первого индекса чего-либо в массиве?


463

Я знаю, что есть метод для списка Python, который возвращает первый индекс чего-либо:

>>> l = [1, 2, 3]
>>> l.index(2)
1

Есть ли что-то подобное для массивов NumPy?


Ответы:


523

Да, вот ответ для массива NumPy arrayи значения itemдля поиска:

itemindex = numpy.where(array==item)

В результате получается кортеж с сначала всеми индексами строк, а затем всеми индексами столбцов.

Например, если массив имеет два измерения и содержит ваш элемент в двух местах, то

array[itemindex[0][0]][itemindex[1][0]]

будет равна вашему предмету, и поэтому будет

array[itemindex[0][1]][itemindex[1][1]]

numpy.where


1
Если вы ищете первую строку, в которой элемент существует в первом столбце, это работает (хотя это приведет к ошибке индекса, если ее не существует)rows, columns = np.where(array==item); first_idx = sorted([r for r, c in zip(rows, columns) if c == 0])[0]
BrT

29
Что если вы хотите, чтобы он прекратил поиск после нахождения первого значения? Я не думаю, что где () сопоставим, чтобы найти ()
Майкл Клеркс

2
Ах! Если вы заинтересованы в производительности, ознакомьтесь с ответом на этот вопрос: stackoverflow.com/questions/7632963/…
Майкл Клеркс

11
np.argwhereбыло бы немного более полезным здесь:itemindex = np.argwhere(array==item)[0]; array[tuple(itemindex)]
Эрик

3
Стоит отметить, что этот ответ предполагает, что массив является 2D. whereработает с любым массивом и возвращает кортеж длины 3 при использовании на массиве 3D и т. д.
P. Camilleri

70

Если вам нужен индекс первого вхождения только одного значения , вы можете использовать nonzero(или where, что в данном случае равно):

>>> t = array([1, 1, 1, 2, 2, 3, 8, 3, 8, 8])
>>> nonzero(t == 8)
(array([6, 8, 9]),)
>>> nonzero(t == 8)[0][0]
6

Если вам нужен первый индекс каждого из множества значений , вы, очевидно, можете делать то же самое, что и выше, но есть хитрость, которая может быть быстрее. Следующее находит индексы первого элемента каждой подпоследовательности :

>>> nonzero(r_[1, diff(t)[:-1]])
(array([0, 3, 5, 6, 7, 8]),)

Обратите внимание, что он находит начало как подпоследовательности 3s, так и обеих подпоследовательностей 8s:

[ 1 , 1, 1, 2 , 2, 3 , 8 , 3 , 8 , 8]

Так что это немного отличается от поиска первого вхождения каждого значения. В вашей программе вы можете работать с отсортированной версией, tчтобы получить то, что вы хотите:

>>> st = sorted(t)
>>> nonzero(r_[1, diff(st)[:-1]])
(array([0, 3, 5, 7]),)

4
Не могли бы вы объяснить, что это r_такое?
Джефф

1
@Geoff, r_объединяет; или, точнее, он переводит объекты среза в конкатенацию по каждой оси. Я мог бы использовать hstackвместо этого; это могло быть менее запутанным. См. Документацию для получения дополнительной информации о r_. Существует также c_.
Вебьорн Лёса

+1, приятный! (против NP.where) ваше решение намного проще (и, вероятно, быстрее) в случае, когда нам нужно только первое вхождение заданного значения в одномерном массиве
doug

3
Последний случай (поиск первого индекса всех значений) задаетсяvals, locs = np.unique(t, return_index=True)
askewchan

@askewchan ваша версия функционально эквивалентна, но намного, намного, намного медленнее
Дживан

50

Вы также можете преобразовать массив NumPy в список и получить его индекс. Например,

l = [1,2,3,4,5] # Python list
a = numpy.array(l) # NumPy array
i = a.tolist().index(2) # i will return index of 2
print i

Это напечатает 1.


Возможно, библиотека изменилась с момента ее написания. Но это было первое решение, которое сработало для меня.
amracel

1
Я хорошо использовал это, чтобы найти несколько значений в списке, используя понимание списка:[find_list.index(index_list[i]) for i in range(len(index_list))]
Мэтт Уэнам,

1
@MattWenham Если он достаточно большой, вы можете преобразовать find_listего в массив NumPy object(или что-то более конкретное, что подходит) и просто сделать find_arr[index_list].
Нарфанар

Абсолютно не по теме, но я впервые вижу фразу «в воздухе» - то, что я видел больше всего, на ее месте, вероятно, «на лету».
flow2k

18

Просто чтобы добавить очень производительный и удобный Альтернатива на основе np.ndenumerateпоиска первого индекса:

from numba import njit
import numpy as np

@njit
def index(array, item):
    for idx, val in np.ndenumerate(array):
        if val == item:
            return idx
    # If no item was found return None, other return types might be a problem due to
    # numbas type inference.

Это довольно быстро и естественно работает с многомерными массивами :

>>> arr1 = np.ones((100, 100, 100))
>>> arr1[2, 2, 2] = 2

>>> index(arr1, 2)
(2, 2, 2)

>>> arr2 = np.ones(20)
>>> arr2[5] = 2

>>> index(arr2, 2)
(5,)

Это может быть намного быстрее (потому что это закорачивает операцию), чем любой подход, использующий np.whereили np.nonzero.


Однако np.argwhereможет также изящно работать с многомерными массивами (вам нужно будет вручную привести его к кортежу, и он не будет закорочен), но он потерпит неудачу, если совпадение не найдено:

>>> tuple(np.argwhere(arr1 == 2)[0])
(2, 2, 2)
>>> tuple(np.argwhere(arr2 == 2)[0])
(5,)

2
@njitэто сокращение, jit(nopython=True)т.е. функция будет полностью скомпилирована на лету во время первого запуска, так что вызовы интерпретатора Python будут полностью удалены.
Бартоло-Отрит

14

Если вы собираетесь использовать это как индекс для чего-то другого, вы можете использовать логические индексы, если массивы являются трансляционными; вам не нужны явные индексы. Абсолютно простой способ сделать это - просто индексировать на основе истинного значения.

other_array[first_array == item]

Любая логическая операция работает:

a = numpy.arange(100)
other_array[first_array > 50]

Ненулевой метод также принимает логические значения:

index = numpy.nonzero(first_array == item)[0][0]

Два нуля относятся к кортежу индексов (при условии, что first_array равен 1D), а затем к первому элементу в массиве индексов.


10

l.index(x)возвращает наименьшее значение i , так что i является индексом первого появления x в списке.

Можно смело предположить, что index()функция в Python реализована так, что она останавливается после нахождения первого совпадения, и это приводит к оптимальной средней производительности.

Чтобы найти остановку элемента после первого совпадения в массиве NumPy, используйте итератор ( ndenumerate ).

In [67]: l=range(100)

In [68]: l.index(2)
Out[68]: 2

Массив NumPy:

In [69]: a = np.arange(100)

In [70]: next((idx for idx, val in np.ndenumerate(a) if val==2))
Out[70]: (2L,)

Обратите внимание, что оба метода index()и nextвозвращают ошибку, если элемент не найден. С помощью nextможно использовать второй аргумент для возврата специального значения в случае, если элемент не найден, например,

In [77]: next((idx for idx, val in np.ndenumerate(a) if val==400),None)

Есть и другие функции в NumPy ( argmax, whereи nonzero) , которые могут быть использованы для поиска элемента в массиве, но все они имеют недостаток , проходящие через весь массив в поисках всех вхождений, таким образом , не оптимизирована для нахождения первого элемента. Обратите внимание, что whereи nonzeroвозвращают массивы, поэтому вам нужно выбрать первый элемент, чтобы получить индекс.

In [71]: np.argmax(a==2)
Out[71]: 2

In [72]: np.where(a==2)
Out[72]: (array([2], dtype=int64),)

In [73]: np.nonzero(a==2)
Out[73]: (array([2], dtype=int64),)

Сравнение времени

Просто проверяя, что для больших массивов решение, использующее итератор, быстрее, когда искомый элемент находится в начале массива (используя %timeitв оболочке IPython):

In [285]: a = np.arange(100000)

In [286]: %timeit next((idx for idx, val in np.ndenumerate(a) if val==0))
100000 loops, best of 3: 17.6 µs per loop

In [287]: %timeit np.argmax(a==0)
1000 loops, best of 3: 254 µs per loop

In [288]: %timeit np.where(a==0)[0][0]
1000 loops, best of 3: 314 µs per loop

Это открытая проблема NumPy GitHub .

Смотрите также: Numpy : быстро найти первый индекс значения


1
Я думаю, что вы должны также указать время для наихудшего случая (последний элемент), чтобы читатели знали, что с ними происходит в худшем случае, когда они используют ваш подход.
MSeifert

@MSeifert Я не могу получить разумные сроки для решения итератора для наихудшего случая - я собираюсь удалить этот ответ, пока не
выясню

1
не %timeit next((idx for idx, val in np.ndenumerate(a) if val==99999))работает? Если вам интересно, почему он работает в 1000 раз медленнее - это потому, что петли Python над массивами Numpy общеизвестно медленны.
Майферт

@MSeifert Нет, я не знал этого, но я также озадачен тем, что argmaxи whereв этом случае гораздо быстрее (поиск элемента в конце массива)
user2314737

Они должны быть такими же быстрыми, как если бы элемент находился в начале. Они всегда обрабатывают весь массив, поэтому они всегда занимают одно и то же время (по крайней мере, так и должно).
MSeifert

9

Для одномерных отсортированных массивов было бы намного проще и эффективнее O (log (n)) использовать numpy.searchsorted, который возвращает целое число NumPy (позиция). Например,

arr = np.array([1, 1, 1, 2, 3, 3, 4])
i = np.searchsorted(arr, 3)

Просто убедитесь, что массив уже отсортирован

Также проверьте, что возвращаемый индекс i действительно содержит искомый элемент, так как главная цель searchsorted - найти индексы, в которые должны быть вставлены элементы для поддержания порядка.

if arr[i] == 3:
    print("present")
else:
    print("not present")

2
searchsorted не nlog (n), так как он не сортирует массив перед поиском, он предполагает, что массив аргументов уже отсортирован. ознакомьтесь с документацией по numpy.searchsorted (ссылка выше)
Alok Nayak

6

Для индексации по любым критериям вы можете сделать что-то вроде следующего:

In [1]: from numpy import *
In [2]: x = arange(125).reshape((5,5,5))
In [3]: y = indices(x.shape)
In [4]: locs = y[:,x >= 120] # put whatever you want in place of x >= 120
In [5]: pts = hsplit(locs, len(locs[0]))
In [6]: for pt in pts:
   .....:         print(', '.join(str(p[0]) for p in pt))
4, 4, 0
4, 4, 1
4, 4, 2
4, 4, 3
4, 4, 4

И вот быстрая функция, чтобы сделать то, что делает list.index (), за исключением того, что не вызывает исключение, если оно не найдено. Осторожно - это, вероятно, очень медленно на больших массивах. Вы, вероятно, можете использовать это для массивов, если вы предпочитаете использовать его как метод.

def ndindex(ndarray, item):
    if len(ndarray.shape) == 1:
        try:
            return [ndarray.tolist().index(item)]
        except:
            pass
    else:
        for i, subarray in enumerate(ndarray):
            try:
                return [i] + ndindex(subarray, item)
            except:
                pass

In [1]: ndindex(x, 103)
Out[1]: [4, 0, 3]

5

Для 1D массивов, я рекомендовал бы np.flatnonzero(array == value)[0], что эквивалентно , как np.nonzero(array == value)[0][0]и np.where(array == value)[0][0]но избегает уродства распаковки с 1-элементным кортежем.


4

Альтернативой выбору первого элемента из np.where () является использование выражения генератора вместе с перечислением, например:

>>> import numpy as np
>>> x = np.arange(100)   # x = array([0, 1, 2, 3, ... 99])
>>> next(i for i, x_i in enumerate(x) if x_i == 2)
2

Для двумерного массива можно сделать:

>>> x = np.arange(100).reshape(10,10)   # x = array([[0, 1, 2,... 9], [10,..19],])
>>> next((i,j) for i, x_i in enumerate(x) 
...            for j, x_ij in enumerate(x_i) if x_ij == 2)
(0, 2)

Преимущество этого подхода состоит в том, что он прекращает проверку элементов массива после того, как найдено первое совпадение, тогда как np.where проверяет все элементы на совпадение. Выражение генератора будет быстрее, если в массиве есть совпадение.


Если в массиве может вообще не быть совпадения, этот метод также позволяет вам удобно указать запасное значение. Если первый пример будет возвращен Noneкак запасной вариант, он станет next((i for i, x_i in enumerate(x) if x_i == 2), None).
Эрленд Магнус Вигген

4

В NumPy существует множество операций, которые можно объединить для достижения этой цели. Это вернет индексы элементов, равные item:

numpy.nonzero(array - item)

Затем вы можете взять первые элементы списков, чтобы получить один элемент.


5
разве это не даст индексы всех элементов, которые не равны элементу?
Автоплектика

3

Пакет numpy_indexed (заявление об отказе от ответственности, я его автор) содержит векторизованный эквивалент list.index для numpy.ndarray; это:

sequence_of_arrays = [[0, 1], [1, 2], [-5, 0]]
arrays_to_query = [[-5, 0], [1, 0]]

import numpy_indexed as npi
idx = npi.indices(sequence_of_arrays, arrays_to_query, missing=-1)
print(idx)   # [2, -1]

Это решение векторизовало производительность, обобщает до ndarrays и имеет различные способы обработки пропущенных значений.


-1

Примечание: это для версии Python 2.7

Вы можете использовать лямбда-функцию для решения проблемы, и она работает как с массивом, так и со списком NumPy.

your_list = [11, 22, 23, 44, 55]
result = filter(lambda x:your_list[x]>30, range(len(your_list)))
#result: [3, 4]

import numpy as np
your_numpy_array = np.array([11, 22, 23, 44, 55])
result = filter(lambda x:your_numpy_array [x]>30, range(len(your_list)))
#result: [3, 4]

И вы можете использовать

result[0]

получить первый индекс отфильтрованных элементов.

Для Python 3.6 используйте

list(result)

вместо

result

Это приводит <filter object at 0x0000027535294D30>к Python 3 (проверено на Python 3.6.3). Возможно обновление для Python 3?
Питер Мортенсен
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.