И NumPy первого вхождения значения больше, чем существующее значение



у меня есть 1D массив в numpy, и я хочу найти позицию индекса, где значение превышает значение в массиве numpy.



например.



aa = range(-10,10)


найти место в aa где значение 5 получает превысил.

599   7  

7 ответов:

Это немного быстрее (и выглядит лучше)

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

учитывая отсортированное содержимое вашего массива, есть еще более быстрый метод: 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
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

Я тоже был заинтересован в этом и я сравнил все предложенные ответы с perfplot. (Отказ от ответственности: я автор perfplot.)

Если вы знаете, что массив, который вы просматриваете,уже отсортированный, потом

numpy.searchsorted(a, alpha)

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

Если вы ничего не знаете о Ваш массив, вы не ошибетесь с

numpy.argmax(a > alpha)

уже разобрались:

enter image description here

несортированный:

enter image description here

код для воспроизведения сюжета:

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)'
    )

массивы, которые имеют постоянный шаг между элементами

в случае 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

Benchmark

несмотря на то, что Нико Шлемер уже предоставил некоторые критерии, я подумал, что было бы полезно включить мои новые решения и протестировать их на разные "значения".

настройки тест:

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")

enter image description here

в функция numba лучше всего выполняет функцию calculate-function и функцию searchsorted. Другие решения работают гораздо хуже.

пункт находится в конце

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

enter image description here

для небольших массивов функция numba работает удивительно быстро, однако для больших массивов она превосходит функцию calculate-function и функцию searchsorted.

пункт в 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")

enter image description here

это более интересно. Опять же numba и функция calculate отлично работают, однако это на самом деле вызывает наихудший случай поиска, который действительно не работает хорошо в этом случае.

сравнение функций, когда ни одно значение не удовлетворяет условию

еще один интересный момент заключается в том, как эти функции ведут себя, если нет значения, индекс которого должен быть вернулся:

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 просто возвращают неверное значение. Однако searchsorted и numba вернуть индекс, который не является допустимым индексом для массива.

функции where,min,nonzero и calculate бросать исключение. Однако только исключение для calculate на самом деле говорит что-нибудь полезное.

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


Примечание: вычислить и searchsorted опции работают только в особых условиях. Функция "вычислить" требует постоянного шага, а функция searchsorted требует сортировки массива. Так что они могут быть полезны в нужных обстоятельствах, но не общие решения этой проблемы. На случай, если ты ... дело с отсортированный Python списки вы можете взглянуть на разделить пополам модуль вместо использования numpys searchsorted.

Я бы поехал с

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

здесь V вектор (1D массив),x и i - это результирующий показатель.

Я хотел бы предложить

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

это вернет наименьший индекс, где выполняется условие, при этом возвращая бесконечность, если условие никогда не выполняется (и where возвращает пустой массив).

Comments

    Ничего не найдено.