Включает в себя: найти первый Индекс стоимости быстро


Как я могу найти индекс первого вхождения числа в массив NumPy? Скорость важна для меня. Меня не интересуют следующие ответы, потому что они сканируют весь массив и не останавливаются, когда находят первое вхождение:

itemindex = numpy.where(array==item)[0][0]
nonzero(array == item)[0][0]

Примечание 1: ни один из ответов на этот вопрос не кажется уместным есть ли функция Numpy для возврата первого индекса чего-то в массиве?

примечание 2: использование C-скомпилированного метода предпочтительнее Python петля.

14 84

14 ответов:

для этого запланирован запрос функции для Numpy 2.0.0:https://github.com/numpy/numpy/issues/2269

хотя это слишком поздно для вас, но на будущее: Используя numba (1) это самый простой способ, пока numpy не реализует его. Если вы используете дистрибутив Anaconda python, он уже должен быть установлен. Код будет скомпилирован, так что это будет быстро.

@jit(nopython=True)
def find_first(item, vec):
    """return the index of the first occurence of item in vec"""
    for i in xrange(len(vec)):
        if item == vec[i]:
            return i
    return -1

и затем:

>>> a = array([1,7,8,32])
>>> find_first(8,a)
2

Я сделал тест для нескольких методов:

  • argwhere
  • nonzero как в вопросе
  • .tostring() как в ответе @Rob Reilink
  • питон цикла
  • Фортран цикла

The Python и Фортран код доступны. Я пропустил неперспективные, такие как преобразование в список.

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

benchmark results

массив имел 1 миллион элементов и тесты были выполнены 100 раз. Результаты все еще немного колеблются, но качественная тенденция ясна: Python и f2py выходят на первый элемент, поэтому они масштабируются по-разному. Python становится слишком медленным, если игла не находится в первом 1%, тогда как f2py быстро (но вам нужно его скомпилировать).

подведем итог f2py-это самое быстрое решение, особенно если игла появляется довольно рано.

он не встроен, что раздражает, но на самом деле это всего лишь 2 минуты работы. Добавить этой в файл с именем search.f90:

subroutine find_first(needle, haystack, haystack_length, index)
    implicit none
    integer, intent(in) :: needle
    integer, intent(in) :: haystack_length
    integer, intent(in), dimension(haystack_length) :: haystack
!f2py intent(inplace) haystack
    integer, intent(out) :: index
    integer :: k
    index = -1
    do k = 1, haystack_length
        if (haystack(k)==needle) then
            index = k - 1
            exit
        endif
    enddo
end

если вы ищете что-то другое, чем integer, просто измените тип. Затем скомпилировать с помощью:

f2py -c -m search search.f90

после чего вы можете сделать (от Python):

import search
print(search.find_first.__doc__)
a = search.find_first(your_int_needle, your_int_array)

вы можете преобразовать логический массив в строку Python с помощью array.tostring() а затем с помощью метода find ():

(array==item).tostring().find('\x01')

это связано с копированием данных, хотя, поскольку строки Python должны быть неизменяемыми. Преимущество заключается в том, что вы также можете искать, например, восходящий край, находя \x00\x01

в случае отсортированных массивов np.searchsorted строительство.

Я думаю, что вы попали в проблему, где другой метод и некоторые априори знание массива действительно поможет. Такая вещь, где у вас есть X вероятность найти свой ответ в первом y проценте данных. Разделение проблемы с надеждой на удачу, а затем сделать это в python с вложенным пониманием списка или чем-то еще.

написание функции C для этого грубой силы не слишком сложно использовать ctypes любой.

код C, который я взломал вместе (индекс.в):

long index(long val, long *data, long length){
    long ans, i;
    for(i=0;i<length;i++){
        if (data[i] == val)
            return(i);
    }
    return(-999);
}

и питон:

# to compile (mac)
# gcc -shared index.c -o index.dylib
import ctypes
lib = ctypes.CDLL('index.dylib')
lib.index.restype = ctypes.c_long
lib.index.argtypes = (ctypes.c_long, ctypes.POINTER(ctypes.c_long), ctypes.c_long)

import numpy as np
np.random.seed(8675309)
a = np.random.random_integers(0, 100, 10000)
print lib.index(57, a.ctypes.data_as(ctypes.POINTER(ctypes.c_long)), len(a))

и я 92.

оберните python в правильную функцию и там вы идете.

версия C намного (~20x) быстрее для этого семени (предупреждение я не очень хорошо с timeit)

import timeit
t = timeit.Timer('np.where(a==57)[0][0]', 'import numpy as np; np.random.seed(1); a = np.random.random_integers(0, 1000000, 10000000)')
t.timeit(100)/100
# 0.09761879920959472
t2 = timeit.Timer('lib.index(57, a.ctypes.data_as(ctypes.POINTER(ctypes.c_long)), len(a))', 'import numpy as np; np.random.seed(1); a = np.random.random_integers(0, 1000000, 10000000); import ctypes; lib = ctypes.CDLL("index.dylib"); lib.index.restype = ctypes.c_long; lib.index.argtypes = (ctypes.c_long, ctypes.POINTER(ctypes.c_long), ctypes.c_long) ')
t2.timeit(100)/100
# 0.005288000106811523

Если ваш список отсортированный, можно добиться очень быстро поиск индекса с пакетом' bisect'. Это O (log (n)) вместо O(n).

bisect.bisect(a, x)

находит x в массиве a, определенно быстрее в отсортированном случае, чем любая c-подпрограмма, проходящая через все первые элементы (для достаточно длинных списков).

это хорошо знать иногда.

@tal уже представил a numba функция для поиска первого индекса, но это работает только для 1D массивов. С 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
    return None

пример:

>>> arr = np.arange(9).reshape(3,3)
>>> index(arr, 3)
(1, 0)

тайминги показывают, что он похож по производительности на tals устранение:

arr = np.arange(100000)
%timeit index(arr, 5)           # 1000000 loops, best of 3: 1.88 µs per loop
%timeit find_first(5, arr)      # 1000000 loops, best of 3: 1.7 µs per loop

%timeit index(arr, 99999)       # 10000 loops, best of 3: 118 µs per loop
%timeit find_first(99999, arr)  # 10000 loops, best of 3: 96 µs per loop

насколько я знаю только np.любой и НП.все логические массивы закорочены.

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

моя рекомендация в этом случае будет использовать cython. Я думаю, что это должно быть легко настроить пример для этого случая, особенно если вам не нужна большая гибкость для разных типов dtypes и форм.

Мне это было нужно для моей работы, поэтому я научил себя интерфейсу Python и Numpy C и написал свой собственный. http://pastebin.com/GtcXuLyd это только для 1-D массивов, но работает для большинства типов данных (int, float или strings), и тестирование показало, что это снова примерно в 20 раз быстрее, чем ожидаемый подход в чистом Python-numpy.

просто обратите внимание, что если вы выполняете последовательность поиска, выигрыш в производительности от выполнения чего-то умного, например преобразования в строку, может быть потерян во внешнем цикле, если размер поиска недостаточно велик. Смотрите, как производительность итерации find1, которая использует трюк преобразования строк, предложенный выше, и find2, который использует argmax вдоль внутренней оси (плюс корректировка, чтобы гарантировать, что несоответствие возвращается как -1)

import numpy,time
def find1(arr,value):
    return (arr==value).tostring().find('\x01')

def find2(arr,value): #find value over inner most axis, and return array of indices to the match
    b = arr==value
    return b.argmax(axis=-1) - ~(b.any())


for size in [(1,100000000),(10000,10000),(1000000,100),(10000000,10)]:
    print(size)
    values = numpy.random.choice([0,0,0,0,0,0,0,1],size=size)
    v = values>0

    t=time.time()
    numpy.apply_along_axis(find1,-1,v,1)
    print('find1',time.time()-t)

    t=time.time()
    find2(v,1)
    print('find2',time.time()-t)

выходы

(1, 100000000)
('find1', 0.25300002098083496)
('find2', 0.2780001163482666)
(10000, 10000)
('find1', 0.46200013160705566)
('find2', 0.27300000190734863)
(1000000, 100)
('find1', 20.98099994659424)
('find2', 0.3040001392364502)
(10000000, 10)
('find1', 206.7590000629425)
('find2', 0.4830000400543213)

что сказать, найти написанный на C будет по крайней мере немного быстрее, чем любой из этих подходов

Как насчет этого

import numpy as np
np.amin(np.where(array==item))

как давний пользователь matlab я уже давно ищу эффективное решение этой проблемы. Наконец, мотватед обсуждениями пропозиции в этом thread Я попытался придумать решение, которое реализует API, подобный тому, что было предложено здесь, поддерживая на данный момент только 1D массивы. Для эффективности расширение написано на C и поэтому должно быть довольно эффективным.

вы находите источник, бенчмарки и другие подробности здесь:

https://pypi.python.org/pypi?name=py_find_1st&:action=display

для использования в нашей команде (anaconda на linux и macos) я сделал установщик anaconda, который упрощает установку, вы можете использовать его, как описано здесь

https://anaconda.org/roebel/py_find_1st

вы можете скрыть свой массив в list использовать index() способ:

i = list(array).index(item)

насколько мне известно, это c-скомпилированный метод.