Numpy : 빠른 가치의 첫 번째 인덱스 찾기


105

Numpy 배열에서 숫자가 처음 나타나는 인덱스를 어떻게 찾을 수 있습니까? 나에게는 속도가 중요합니다. 나는 전체 배열을 스캔하고 첫 번째 발생을 찾을 때 멈추지 않기 때문에 다음 답변에 관심이 없습니다.

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

참고 1 : 해당 질문의 답변 중 어느 것도 관련 이없는 것 같습니다 . 배열에서 무언가의 첫 번째 인덱스를 반환하는 Numpy 함수가 있습니까?

참고 2 : C 컴파일 된 메서드를 사용하는 것이 Python 루프보다 선호됩니다.

답변:



30

너무 늦었지만 나중에 참조 할 수 있습니다. numba ( 1 )를 사용 하는 것이 numpy가 구현할 때까지 가장 쉬운 방법입니다. 아나콘다 파이썬 배포판을 사용한다면 이미 설치되어있을 것입니다. 코드가 컴파일되므로 빠릅니다.

@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

4
python3의 xrange경우 range.

Python 3+에서 약간의 코드 개선 : use enumerate, as in for i, v in enumerate(vec):; if v == item: return i. (이것은 기본 반복자가 아닌 목록을 생성하는 Python <= 2.7에서는 좋은 생각 이 아닙니다enumerate .)
acdr

23

몇 가지 방법에 대한 벤치 마크를 만들었습니다.

  • argwhere
  • nonzero 질문에서와 같이
  • .tostring() @Rob Reilink의 답변에서와 같이
  • 파이썬 루프
  • Fortran 루프

파이썬포트란 코드를 사용할 수 있습니다. 나는 목록으로 변환하는 것과 같이 유망하지 않은 것을 건너 뛰었습니다.

로그 스케일의 결과. X 축은 바늘의 위치입니다 (배열 아래에 있는지 찾는 데 시간이 더 오래 걸립니다). 마지막 값은 배열에없는 바늘입니다. Y 축은 그것을 찾을 시간입니다.

벤치 마크 결과

어레이에는 1 백만 개의 요소가 있으며 테스트는 100 번 실행되었습니다. 결과는 여전히 약간 변동이 있지만 정성적인 추세는 분명합니다. Python과 f2py는 첫 번째 요소에서 종료되어 다른 방식으로 확장됩니다. 바늘이 처음 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)

2
f2py1 개 항목이 10 개보다 느린 이유는 무엇 입니까?
에릭

2
@Eric, 내 추측은 그 척도 (10e-6)에서 데이터의 소음이며 실제 항목 당 속도가 너무 빠르기 때문에 n <100 정도에서 전체 시간에 의미있게 기여하지 않습니다
Brendan

11

array.tostring()다음과 같이 find () 메서드를 사용 하여 부울 배열을 Python 문자열로 변환 할 수 있습니다 .

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

하지만 파이썬 문자열은 변경 불가능해야하므로 데이터 복사가 포함됩니다. 장점은 예를 들어 상승 에지를 찾아서 검색 할 수도 있다는 것입니다.\x00\x01


이것은 흥미롭지 만 모든 데이터를 처리해야하기 때문에 거의 빠르지 않습니다 (벤치 마크는 내 답변 참조).
Mark


7

다른 방법과 어레이에 대한 사전 지식이 실제로 도움 이되는 문제에 부딪혔다 고 생각합니다 . 데이터의 처음 Y %에서 답을 찾을 확률이 X 인 경우입니다. 운이 좋을 것이라는 희망으로 문제를 나누고 중첩 된 목록 이해 또는 무언가로 파이썬에서 이것을 수행합니다.

이 무차별 대입을 수행하기 위해 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를 얻습니다.

파이썬을 적절한 기능으로 감싸면됩니다.

C 버전은이 시드에 대해 훨씬 빠릅니다 (~ 20 배).

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

1
배열이 double 인 경우 (python floats는 기본적으로 C doubles임을 기억하십시오) ==가 실제로 안전하지 않거나 부동 소수점 값에 대해 원하는 것이기 때문에 조금 더 어렵게 생각해야합니다. 또한 ctypes를 사용하여 numpy 배열을 입력 할 때 정말 좋은 생각이라는 것을 잊지 마십시오.
Brian Larsen

감사합니다 @Brian Larsen. 나는 그것을 시도 할 수 있습니다. 다음 numpy 개정에 대한 사소한 기능 요청이라고 생각합니다.
사이보그

5

@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

1
더 나아가 주어진 축을 따라 먼저 검색하는 데 관심이있는 경우 :에 array공급하기 전에 조옮김하여 관심 축이 먼저 오도록 np.ndenumerate합니다.
CheshireCat

감사합니다. 이것은 실제로 크기가 훨씬 빠릅니다. ~ 171ms ( np.argwhere)에서 717ns (귀하의 솔루션)까지, 둘 다 형태의 배열에 대해 (3000000, 12)).
Arthur Colombini Gusmão

3

목록이 정렬 된 경우 'bisect'패키지를 사용하여 색인을 매우 빠르게 검색 할 수 있습니다 . O (n) 대신 O (log (n))입니다.

bisect.bisect(a, x)

배열 a에서 x를 찾습니다. 정렬 된 경우 모든 C- 루틴이 모든 첫 번째 요소를 통과하는 것보다 훨씬 빠릅니다 (충분한 목록의 경우).

때때로 아는 것이 좋습니다.


>>> cond = "import numpy as np;a = np.arange(40)" timeit("np.searchsorted(a, 39)", cond)3.47867107391 초 동안 작동합니다. timeit("bisect.bisect(a, 39)", cond2)7.0661458969116 초 동안 작동합니다. 그것은 모양 numpy.searchsorted(최소의 int에 대한) 정렬 된 배열 좋습니다.
Boris Tsema 2014 년

2

내가 아는 한 부울 배열의 np.any 및 np.all 만 단락됩니다.

귀하의 경우 numpy는 부울 조건을 생성하기 위해 한 번, 인덱스를 찾기 위해 두 번 전체 배열을 두 번 통과해야합니다.

이 경우 내 권장 사항은 cython을 사용하는 것입니다. 특히 다른 dtype과 모양에 대해 많은 유연성이 필요하지 않은 경우이 경우에 대한 예제를 조정하는 것이 쉬워야한다고 생각합니다.


2

나는 이것이 내 직업에 필요했기 때문에 Python과 Numpy의 C 인터페이스를 스스로 가르치고 직접 작성했습니다. http://pastebin.com/GtcXuLyd 1D 배열에만 해당되지만 대부분의 데이터 유형 (int, float 또는 string)에서 작동하며 테스트 결과 순수 Python에서 예상되는 접근 방식보다 약 20 배 더 빠릅니다. numpy.


2

이 문제는 배열을 청크로 처리하여 순수 numpy로 효과적으로 해결할 수 있습니다.

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이상 빠른 단계이다 제로 어레이 (최악의 경우)을 처리한다. 작을수록 처음에 0이 아닌 배열의 처리 속도가 빨라집니다. 트릭은 작은 것으로 시작하여 step기하 급수적으로 늘리는 것입니다. 또한 제한된 혜택으로 인해 일부 임계 값 이상으로 증가시킬 필요가 없습니다.

순수한 ndarary.nonzero 및 numba 솔루션과 천만 개의 부동 소수점 배열을 비교했습니다.

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

Pure ndarray.nonzero는 확실히 더 느슨합니다. numba 솔루션은 최상의 경우에 대해 약 5 배 더 빠릅니다. 최악의 경우 약 3 배 더 빠릅니다.


2

0이 아닌 첫 번째 요소를 찾고 있다면 다음 해킹을 사용할 수 있습니다.

idx = x.view(bool).argmax() // x.itemsize
idx = idx if x[idx] else -1

그것은이다 매우 빠른 "NumPy와 순수"솔루션하지만 어떤 경우는 아래에 설명을 위해 실패합니다.

이 솔루션은 숫자 유형에 대한 거의 모든 0 표현이 0바이트 로 구성된다는 사실을 활용 합니다. numpy에도 적용됩니다 bool. 최신 버전의 numpy에서 argmax()함수는 bool유형을 처리 할 때 단락 논리를 사용 합니다. 의 크기 bool는 1 바이트입니다.

따라서 다음이 필요합니다.

  • 배열의 뷰를 bool. 사본이 생성되지 않습니다.
  • 사용 argmax()단락 논리를 사용하여 상기 제 비제로 바이트가 발견
  • 이 바이트의 오프셋을 바이트로 //표현 된 단일 요소의 크기로 오프셋을 정수 나눗셈 (연산자 )으로 0이 아닌 첫 번째 요소의 인덱스로 다시 계산합니다 ( x.itemsize).
  • x[idx]실제로 0이 아닌지 확인하여 0이 아닌 경우를 식별합니다.

나는 numba 솔루션에 대한 몇 가지 벤치 마크를 만들고 그것을 구축했습니다 np.nonzero.

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

def find_first(x):
    idx = x.view(bool).argmax() // x.itemsize
    return idx if x[idx] else -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 57.63976670001284 ms
find_first 0.0010841979965334758 ms
find_first_numba 0.0002308919938514009 ms
---- LAST ----
ndarray.nonzero 58.96685277999495 ms
find_first 5.923203580023255 ms
find_first_numba 8.762269750004634 ms
---- NONE ----
ndarray.nonzero 25.13398071998381 ms
find_first 5.924289370013867 ms
find_first_numba 8.810063839919167 ms
---- ALL ----
ndarray.nonzero 55.181210660084616 ms
find_first 0.001246920000994578 ms
find_first_numba 0.00028766007744707167 ms

솔루션은 numba보다 33 % 빠르며 "numpy-pure"입니다.

단점 :

  • 다음과 같은 numpy 허용 유형에서는 작동하지 않습니다. object
  • 가끔 나타나는 음의 0 float또는 double계산에 실패합니다.

이것은 시도한 최고의 순수한 numpy 솔루션입니다. 받아 들여 져야합니다. @tstanisl ive는 배열에서 첫 번째 0 요소를 찾기 위해 비슷하게 빠른 솔루션을 얻으려고 노력했지만 항상 bool로 변환 한 다음 argmin ()을 실행하는 것보다 느립니다. 어떤 아이디어?
Ta946

1
@ Ta946. 0 항목을 찾을 때 트릭을 사용할 수 없습니다. 예를 들어, 0이 아닌 double에는 0 바이트가 포함될 수 있습니다. numpy-pure 솔루션을 찾으면 다른 답변 을 수정하십시오 . stackoverflow.com/a/58294774/4989451을 참조하십시오 . x를 호출하기 전에 조각을 부정하십시오 nonzero(). numba보다 느릴 가능성이 높지만 첫 번째 0 항목을 찾는 동안 전체 배열을 ** 검색하지 않습니다 **. 따라서 필요에 따라 충분히 빠를 수 있습니다.
tstanisl

1

오랜 matlab 사용자로서 나는 꽤 오랫동안이 문제에 대한 효율적인 솔루션을 찾고 있습니다. 마지막으로,이 스레드 의 제안에 대한 토론을 통해 여기 에서 제안 된 것과 유사한 API를 구현하는 솔루션을 생각해 보았습니다 . 현재로서는 1D 배열 만 지원합니다.

당신은 이것을 이렇게 사용할 것입니다

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

우리 팀에서 사용하기 위해 (리눅스 및 macOS의 아나콘다) 설치를 단순화하는 아나콘다 설치 프로그램을 만들었습니다. 여기에 설명 된대로 사용할 수 있습니다.

https://anaconda.org/roebel/py_find_1st


"오랜 MATLAB 사용자로서" -이에 대한 matlab 철자는 무엇입니까?
Eric

find (X, n)은 X가 0이 아닌 처음 n 개의 인덱스를 찾습니다. mathworks.com/help/matlab/ref/find.html
Roebel에게

0

일련의 검색을 수행하는 경우 검색 차원이 충분히 크지 않으면 외부 루프에서 문자열로 변환하는 것과 같은 영리한 작업을 수행하여 얻을 수있는 성능 이점이 손실 될 수 있습니다. 위에서 제안한 문자열 변환 트릭을 ​​사용하는 find1과 내부 축을 따라 argmax를 사용하는 find2를 반복하는 성능을 확인하십시오 (불일치가 -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로 작성된 발견은 이러한 접근 방식 중 하나보다 조금 더 빠릅니다.


0

이건 어때요

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

2
이 코드는 질문에 답할 수 있지만 질문에 대한 이유 및 / 또는 답변 방법 에 대한 추가 컨텍스트를 제공 하면 장기적인 가치가 크게 향상됩니다. 제발 편집 약간의 설명을 추가 할 답변을.
Toby Speight

1
나는 이것이 where(array==item)[0][0]질문 보다 더 느리다고 확신합니다 ...
Mark

-1

배열을 a로 숨기고 방법을 list사용할 수 있습니다 index().

i = list(array).index(item)

내가 아는 한 이것은 C 컴파일 방법입니다.


3
이것은 np.where에서 첫 번째 결과를 취하는 것보다 몇 배 더 느릴 것입니다
cwa

1
매우 사실입니다. 저는 timeit()10000 개의 정수 배열을 사용 했습니다. 목록으로 변환하는 것이 약 100 배 더 느 렸습니다! numpy 배열의 기본 데이터 구조가 목록과 매우 다르다는 사실을
잊었습니다
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.