Numpy: быстро найти первый индекс ценности

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

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

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

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

Ответ 2

Хотя для вас это слишком поздно, но для дальнейшего использования: Использование 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

Ответ 3

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

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

Код 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)

Ответ 4

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

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

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

Ответ 5

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

Ответ 6

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

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

Код C, который я взломал вместе (index.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

Ответ 7

@tal уже представил функцию 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

Ответ 8

Насколько я знаю, только np.any и np.all в булевых массивах закорочены.

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

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

Ответ 9

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

Ответ 10

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

bisect.bisect(a, x)

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

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

Ответ 11

Как давний пользователь Matlab, я довольно долго искал эффективное решение этой проблемы. Наконец, из-за обсуждения предложений в этой теме я попытался найти решение, которое реализует API, аналогичный тому, который был предложен здесь, на данный момент поддерживая только одномерные массивы,

Вы бы использовали это так

import numpy as np
import utils_find_1st as utf1st
array = np.arange(100000)
item = 1000
ind = utf1st.find_1st(array, item, utf1st.cmp_larger_eq)

Поддерживаются следующие операторы условий: cmp_equal, cmp_not_equal, cmp_larger, cmp_smaller, cmp_larger_eq, cmp_smaller_eq. Для эффективности расширение написано в c.

Вы найдете источник, тесты и другие детали здесь:

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

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

https://anaconda.org/roebel/py_find_1st

Ответ 12

Вы можете получить буфер чтения и записи в массиве numpy с использованием атрибута .data. Перейди на это, но вам нужно знать, являются ли ваши данные строковыми или столбчатыми (используйте ndarray.shape и numpy.unravel_index для преобразования плоского индекса обратно в индексный кортеж).

Ответ 13

Просто обратите внимание, что если вы выполняете последовательность поисков, выигрыш в производительности от того, чтобы сделать что-то умное, как преобразование в строку, может быть потерян во внешнем цикле, если размер поиска недостаточно велик. Посмотрите, как работает итерация 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, будет, по крайней мере, немного быстрее, чем любой из этих подходов

Ответ 14

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

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

Ответ 15

Эта проблема может быть эффективно решена в чистом виде путем обработки массива кусками:

def find_first(x):
    idx, step = 0, 32
    while idx < x.size:
        nz, = x[idx: idx + step].nonzero()
        if len(nz): # found non-zero, return it
            return nz[0] + idx
        # move to the next chunk, increase step
        idx += step
        step = min(9600, step + step // 2)
    return -1

Массив обрабатывается кусками размера step. Чем step длиннее шаг, тем быстрее обрабатывается нулевой массив (наихудший случай). Чем оно меньше, тем быстрее обрабатывается массив с ненулевым значением в начале. Хитрость заключается в том, чтобы начать с небольшого step и увеличить его в геометрической прогрессии. Более того, нет необходимости увеличивать его выше некоторого порога из-за ограниченных преимуществ.

Я сравнил решение с чистыми решениями ndarary.nonzero и numba с 10 миллионами массивов с плавающей точкой.

import numpy as np
from numba import jit
from timeit import timeit

def find_first(x):
    idx, step = 0, 32
    while idx < x.size:
        nz, = x[idx: idx + step].nonzero()
        if len(nz):
            return nz[0] + idx
        idx += step
        step = min(9600, step + step // 2)
    return -1

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


SIZE = 10_000_000
# First only
x = np.empty(SIZE)

find_first_numba(x[:10])

print('---- FIRST ----')
x[:] = 0
x[0] = 1
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0][0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=1000), 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=1000), 'ms')

print('---- LAST ----')
x[:] = 0
x[-1] = 1
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0][0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=100)*10, 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=100)*10, 'ms')

print('---- NONE ----')
x[:] = 0
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=100)*10, 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=100)*10, 'ms')

print('---- ALL ----')
x[:] = 1
print('ndarray.nonzero', timeit(lambda: x.nonzero()[0][0], number=100)*10, 'ms')
print('find_first', timeit(lambda: find_first(x), number=100)*10, 'ms')
print('find_first_numba', timeit(lambda: find_first_numba(x), number=100)*10, 'ms')

И результаты на моей машине:

---- FIRST ----
ndarray.nonzero 54.733994480002366 ms
find_first 0.0013148509997336078 ms
find_first_numba 0.0002839310000126716 ms
---- LAST ----
ndarray.nonzero 54.56336712999928 ms
find_first 25.38929685000312 ms
find_first_numba 8.022820680002951 ms
---- NONE ----
ndarray.nonzero 24.13432420999925 ms
find_first 25.345200140000088 ms
find_first_numba 8.154927100003988 ms
---- ALL ----
ndarray.nonzero 55.753537260002304 ms
find_first 0.0014760300018679118 ms
find_first_numba 0.0004358099977253005 ms

Чистый ndarray.nonzero - определенно слабее. В лучшем случае решение numba примерно в 5 раз быстрее. В худшем случае это примерно в 3 раза быстрее.

Ответ 16

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

i = list(array).index(item)

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