기존 값보다 큰 값이 처음으로 나타남


144

numpy에 1D 배열이 있고 값이 numpy 배열의 값을 초과하는 인덱스의 위치를 ​​찾고 싶습니다.

예 :

aa = range(-10,10)

aa5이 초과 되는 위치를 찾으십시오 .


2
ambrus가 언급했듯이 (예를 들어 argmax 응답이 최대 (0,0,0,0) = 0) 작동하지 않기 때문에 해결책이 없을 수 있는지 여부를 분명히해야합니다.
seanv507

답변:


199

이것은 조금 더 빠르며 더 멋지게 보입니다.

np.argmax(aa>5)

argmax처음 부터 멈추 므로 True( "최대 값이 여러 번 발생하는 경우 첫 번째 발생에 해당하는 인덱스가 반환됩니다.") 다른 목록을 저장하지 않습니다.

In [2]: N = 10000

In [3]: aa = np.arange(-N,N)

In [4]: timeit np.argmax(aa>N/2)
100000 loops, best of 3: 52.3 us per loop

In [5]: timeit np.where(aa>N/2)[0][0]
10000 loops, best of 3: 141 us per loop

In [6]: timeit np.nonzero(aa>N/2)[0][0]
10000 loops, best of 3: 142 us per loop

103
입력 배열에 True 값이 없으면 np.argmax는 행복하게 0을 반환합니다 (이 경우에는 원하는 것이 아닙니다).
ambrus

8
결과는 정확하지만 설명이 약간 의심 스럽습니다. argmax처음에는 멈추지 않는 것 같습니다 True. (이것은 True다른 위치에 하나의 부울 배열을 만들어 테스트 할 수 있습니다 .) 속도는 아마도 argmax출력 목록을 만들 필요가 없다는 사실에 의해 설명됩니다 .
DrV

1
당신이 옳다고 생각합니다, @DrV. 내 설명은 원래 최대 의도를 찾지는 않았지만 원래 의도에도 불구하고 올바른 결과를 제공하는 이유에 대한 것이 었습니다 argmax.
askewchan

1
@ 조지, 왜 정확한지 모르겠습니다. 나는 내가 보여준 특정 예제에서 더 빠르다고 말할 수 있기 때문에 (i) 이유를 알지 못하거나 (@DrV의 의견 참조) 또는 (ii) 더 많은 사례를 테스트하지 않고 (예 : aa정렬 여부 , @Michael의 답변과 같이).
askewchan

3
@ DrV, NumPy 1.11.2를 사용하여 다른 위치에서 argmax단일 천만 요소 부울 배열 True을 실행하고 True중요한 위치를 실행했습니다 . 따라서 1.11.2 argmax는 부울 배열에서 "단락"하는 것 같습니다.
Ulrich Stern

96

배열의 정렬 된 내용이 주어지면 더 빠른 방법이 있습니다 : searchsorted .

import time
N = 10000
aa = np.arange(-N,N)
%timeit np.searchsorted(aa, N/2)+1
%timeit np.argmax(aa>N/2)
%timeit np.where(aa>N/2)[0][0]
%timeit np.nonzero(aa>N/2)[0][0]

# Output
100000 loops, best of 3: 5.97 µs per loop
10000 loops, best of 3: 46.3 µs per loop
10000 loops, best of 3: 154 µs per loop
10000 loops, best of 3: 154 µs per loop

19
이것은 실제로 배열이 정렬되었다고 가정 할 때 가장 좋은 대답입니다 (실제로는 질문에 지정되지 않음). 당신은 어색한을 피할 수 +1np.searchsorted(..., side='right')
askewchan

3
side정렬 된 배열에 반복되는 값이있는 경우에만 인수가 차이가 있다고 생각합니다 . 반환 된 인덱스의 의미는 변경하지 않습니다. 항상 쿼리 값을 삽입하고 다음 항목을 모두 오른쪽으로 이동하고 정렬 된 배열을 유지할 수있는 인덱스입니다.
Gus

@Gus 는 반복되는 값에 관계없이 side동일한 값이 정렬 된 배열과 삽입 된 배열 모두에 있을 때 효과 가 있습니다. 정렬 된 배열에서 반복되는 값은 효과를 과장합니다 (변의 차이는 정렬 된 배열에 삽입되는 값이 나타나는 횟수입니다). side 않는 것이 그 인덱스의 정렬 된 배열에 값을 삽입하는 결과 배열을 변경하지 않지만, 반환 지수의 의미를 변경. 미묘하지만 중요한 차이점; 사실이 대답 N/2은에없는 경우 잘못된 색인을 제공합니다 aa.
askewchan

위의 주석에서 암시 된 것처럼이 대답이 N/2에 없으면 하나에 의해 해제됩니다 aa. 올바른 형태가 될 것이다 np.searchsorted(aa, N/2, side='right')합니다 (없이 +1). 그렇지 않으면 두 형식 모두 동일한 색인을 제공합니다. N이상한 테스트 사례를 고려하십시오 ( N/2.0파이썬 2를 사용하는 경우 강제로 플로팅해야 함).
askewchan

21

나는 이것에 관심이 있었고 제안 된 모든 답변을 perfplot비교했습니다 . (면책 조항 : 저는 perfplot의 저자입니다.)

당신은 당신을 통해 찾고있는 배열되는 것을 알고 있다면 이미 정렬 한 다음

numpy.searchsorted(a, alpha)

당신을위한 것입니다. 일정한 시간 작동입니다. 즉, 속도는 어레이의 크기에 의존 하지 않습니다 . 당신은 그것보다 빨리 얻을 수 없습니다.

배열에 대해 아무것도 모르면 문제가 없습니다.

numpy.argmax(a > alpha)

이미 정렬 :

여기에 이미지 설명을 입력하십시오

분류되지 않은 :

여기에 이미지 설명을 입력하십시오

줄거리를 재현하는 코드 :

import numpy
import perfplot


alpha = 0.5

def argmax(data):
    return numpy.argmax(data > alpha)

def where(data):
    return numpy.where(data > alpha)[0][0]

def nonzero(data):
    return numpy.nonzero(data > alpha)[0][0]

def searchsorted(data):
    return numpy.searchsorted(data, alpha)

out = perfplot.show(
    # setup=numpy.random.rand,
    setup=lambda n: numpy.sort(numpy.random.rand(n)),
    kernels=[
        argmax, where,
        nonzero,
        searchsorted
        ],
    n_range=[2**k for k in range(2, 20)],
    logx=True,
    logy=True,
    xlabel='len(array)'
    )

4
np.searchsorted일정하지 않습니다. 실제로 O(log(n))입니다. 그러나 테스트 사례는 실제로 최상의 사례 searchsorted(즉 O(1))를 벤치마킹합니다 .
MSeifert

@MSeifert 어떤 종류의 입력 배열 / 알파가 O (log (n))을보아야합니까?
Nico Schlömer

1
인덱스 sqrt (length)에서 항목을 가져 오면 성능이 매우 떨어졌습니다. 또한 그 벤치 마크를 포함하여 여기 에 답변 을 썼습니다 .
MSeifert

나는 searchsorted(또는 모든 알고리즘) O(log(n))정렬 된 균일하게 분포 된 데이터에 대한 이진 검색보다 이길 수 있다고 의심 합니다. 편집 : searchsorted 입니다 이진 검색.
Mateen Ulhaq

16
In [34]: a=np.arange(-10,10)

In [35]: a
Out[35]:
array([-10,  -9,  -8,  -7,  -6,  -5,  -4,  -3,  -2,  -1,   0,   1,   2,
         3,   4,   5,   6,   7,   8,   9])

In [36]: np.where(a>5)
Out[36]: (array([16, 17, 18, 19]),)

In [37]: np.where(a>5)[0][0]
Out[37]: 16

8

요소 사이에 일정한 단계가있는 배열

(A)의 경우 range나 다른 선형 적으로 증가 배열 당신은 단순히 모두의 배열을 통해 실제로 반복, 프로그래밍 필요를 인덱스를 계산할 수 있습니다 :

def first_index_calculate_range_like(val, arr):
    if len(arr) == 0:
        raise ValueError('no value greater than {}'.format(val))
    elif len(arr) == 1:
        if arr[0] > val:
            return 0
        else:
            raise ValueError('no value greater than {}'.format(val))

    first_value = arr[0]
    step = arr[1] - first_value
    # For linearly decreasing arrays or constant arrays we only need to check
    # the first element, because if that does not satisfy the condition
    # no other element will.
    if step <= 0:
        if first_value > val:
            return 0
        else:
            raise ValueError('no value greater than {}'.format(val))

    calculated_position = (val - first_value) / step

    if calculated_position < 0:
        return 0
    elif calculated_position > len(arr) - 1:
        raise ValueError('no value greater than {}'.format(val))

    return int(calculated_position) + 1

아마 조금 향상시킬 수 있습니다. 몇 가지 샘플 배열과 값에 대해 올바르게 작동하는지 확인했지만 실수를 사용할 수 없다는 것을 의미하지는 않습니다. 특히 플로트를 사용한다고 생각하면 ...

>>> import numpy as np
>>> first_index_calculate_range_like(5, np.arange(-10, 10))
16
>>> np.arange(-10, 10)[16]  # double check
6

>>> first_index_calculate_range_like(4.8, np.arange(-10, 10))
15

반복없이 위치를 계산할 수 있다고 가정하면 일정한 시간 ( O(1))이 될 것이고 아마도 언급 된 다른 모든 접근법을 능가 할 수 있습니다. 그러나 배열에서 일정한 단계가 필요합니다. 그렇지 않으면 잘못된 결과가 생성됩니다.

Numba를 사용하는 일반적인 솔루션

보다 일반적인 접근 방식은 numba 함수를 사용하는 것입니다.

@nb.njit
def first_index_numba(val, arr):
    for idx in range(len(arr)):
        if arr[idx] > val:
            return idx
    return -1

그것은 모든 배열에서 작동하지만 배열을 반복해야하므로 평균적인 경우에는 다음과 O(n)같습니다.

>>> first_index_numba(4.8, np.arange(-10, 10))
15
>>> first_index_numba(5, np.arange(-10, 10))
16

기준

Nico Schlömer는 이미 몇 가지 벤치 마크를 제공했지만 새로운 솔루션을 포함하고 다른 "값"을 테스트하는 것이 유용 할 것으로 생각했습니다.

테스트 설정 :

import numpy as np
import math
import numba as nb

def first_index_using_argmax(val, arr):
    return np.argmax(arr > val)

def first_index_using_where(val, arr):
    return np.where(arr > val)[0][0]

def first_index_using_nonzero(val, arr):
    return np.nonzero(arr > val)[0][0]

def first_index_using_searchsorted(val, arr):
    return np.searchsorted(arr, val) + 1

def first_index_using_min(val, arr):
    return np.min(np.where(arr > val))

def first_index_calculate_range_like(val, arr):
    if len(arr) == 0:
        raise ValueError('empty array')
    elif len(arr) == 1:
        if arr[0] > val:
            return 0
        else:
            raise ValueError('no value greater than {}'.format(val))

    first_value = arr[0]
    step = arr[1] - first_value
    if step <= 0:
        if first_value > val:
            return 0
        else:
            raise ValueError('no value greater than {}'.format(val))

    calculated_position = (val - first_value) / step

    if calculated_position < 0:
        return 0
    elif calculated_position > len(arr) - 1:
        raise ValueError('no value greater than {}'.format(val))

    return int(calculated_position) + 1

@nb.njit
def first_index_numba(val, arr):
    for idx in range(len(arr)):
        if arr[idx] > val:
            return idx
    return -1

funcs = [
    first_index_using_argmax, 
    first_index_using_min, 
    first_index_using_nonzero,
    first_index_calculate_range_like, 
    first_index_numba, 
    first_index_using_searchsorted, 
    first_index_using_where
]

from simple_benchmark import benchmark, MultiArgument

그리고 플롯은 다음을 사용하여 생성되었습니다.

%matplotlib notebook
b.plot()

항목이 시작에 있습니다

b = benchmark(
    funcs,
    {2**i: MultiArgument([0, np.arange(2**i)]) for i in range(2, 20)},
    argument_name="array size")

여기에 이미지 설명을 입력하십시오

numba 기능은 가장 잘 수행 된 다음 계산 기능과 검색된 기능을 수행합니다. 다른 솔루션은 훨씬 나빠집니다.

항목이 끝났습니다

b = benchmark(
    funcs,
    {2**i: MultiArgument([2**i-2, np.arange(2**i)]) for i in range(2, 20)},
    argument_name="array size")

여기에 이미지 설명을 입력하십시오

작은 배열의 경우 numba 함수가 놀랍도록 빠르게 수행되지만 더 큰 배열의 경우 계산 함수 및 검색 정렬 함수보다 성능이 뛰어납니다.

항목이 sqrt (len)에 있습니다

b = benchmark(
    funcs,
    {2**i: MultiArgument([np.sqrt(2**i), np.arange(2**i)]) for i in range(2, 20)},
    argument_name="array size")

여기에 이미지 설명을 입력하십시오

이것은 더 흥미 롭습니다. 다시 numba와 계산 함수가 훌륭하게 수행되지만 실제로는이 경우에는 제대로 작동하지 않는 최악의 검색 정렬이 트리거됩니다.

조건에 맞는 값이 없을 때의 기능 비교

또 다른 흥미로운 점은 인덱스를 반환해야하는 값이없는 경우 이러한 함수의 작동 방식입니다.

arr = np.ones(100)
value = 2

for func in funcs:
    print(func.__name__)
    try:
        print('-->', func(value, arr))
    except Exception as e:
        print('-->', e)

이 결과로 :

first_index_using_argmax
--> 0
first_index_using_min
--> zero-size array to reduction operation minimum which has no identity
first_index_using_nonzero
--> index 0 is out of bounds for axis 0 with size 0
first_index_calculate_range_like
--> no value greater than 2
first_index_numba
--> -1
first_index_using_searchsorted
--> 101
first_index_using_where
--> index 0 is out of bounds for axis 0 with size 0

Searchsorted, argmax 및 numba는 단순히 잘못된 값을 반환합니다. 그러나 searchsortednumba배열에 대한 올바른 인덱스가 아닌 인덱스를 돌려줍니다.

기능은 where, min, nonzerocalculate예외를 throw합니다. 그러나 calculate실제로 예외가 도움이되는 것을 말합니다.

즉, 예외 또는 유효하지 않은 반환 값을 포착하고 적어도 값이 배열에 있는지 확실하지 않은 경우 적절한 래퍼 함수로 이러한 호출을 래핑해야합니다.


참고 : 계산 및 searchsorted옵션은 특수 조건에서만 작동합니다. "계산"기능을 사용하려면 일정한 단계가 필요하며 검색 정렬을 위해서는 배열을 정렬해야합니다. 따라서 올바른 환경에서 유용 할 수 있지만 이 문제에 대한 일반적인 해결책 은 아닙니다 . 정렬 된 파이썬 목록을 다루는 경우 Numpys 검색 정렬을 사용하는 대신 bisect 모듈을 살펴볼 수 있습니다.


3

제안하고 싶습니다

np.min(np.append(np.where(aa>5)[0],np.inf))

조건이 충족되지 않으면 무한대를 반환하고 조건이 충족되지 않으면 무한 where배열을 반환하고 빈 배열을 반환합니다.


1

나는 갈 것이다

i = np.min(np.where(V >= x))

여기서 V벡터 (1d 배열) x는 값이며 i결과 색인입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.