Должно быть ясно, не может ли быть никакого решения (поскольку, например, ответ argmax не будет работать в этом случае (max of (0,0,0,0) = 0), как прокомментировал
ambrus
Ответы:
214
Это немного быстрее (и выглядит лучше)
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
Небольшое предупреждение: если во входном массиве нет значения True, np.argmax с радостью вернет 0 (в данном случае это не то, что вам нужно).
ambrus
10
Результаты верны, но я нахожу объяснение немного подозрительным. argmaxпохоже, не останавливается на первом True. (Это можно проверить, создав логические массивы с одним Trueв разных позициях.) Скорость, вероятно, объясняется тем, что argmaxне нужно создавать выходной список.
DrV 08
1
Думаю, ты прав, @DrV. Мое объяснение должно было быть о том, почему он дает правильный результат, несмотря на первоначальное намерение, фактически не стремясь к максимуму, а не о том, почему он быстрее, поскольку я не могу утверждать, что понимаю внутренние детали argmax.
askewchan 08
1
@ Джордж, боюсь, я не знаю почему. Я могу только сказать, что это быстрее в конкретном примере, который я показал, поэтому я бы не стал рассматривать его в целом быстрее, не (i) зная, почему это так (см. Комментарий @ DrV) или (ii) проверяя больше случаев (например, aaсортируется ли , как в ответе @ Michael).
askewchan
3
@DrV, я только что запустил argmaxлогические массивы из 10 миллионов элементов с одним Trueв разных позициях, используя NumPy 1.11.2, и позицию Trueзначения. Таким образом, argmaxкажется, что 1.11.2 "закорачивает" логические массивы.
Ульрих Стерн,
98
учитывая отсортированное содержимое вашего массива, есть еще более быстрый метод: 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]
# Output100000 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
Это действительно лучший ответ при условии, что массив отсортирован (что на самом деле не указано в вопросе). Вы можете избежать +1np.searchsorted(..., side='right')
неудобства
3
Я думаю, что sideаргумент имеет значение только в том случае, если в отсортированном массиве есть повторяющиеся значения. Это не меняет значения возвращаемого индекса, который всегда является индексом, в который вы можете вставить значение запроса, смещая все следующие записи вправо и сохраняя отсортированный массив.
Gus
@Gus sideдействует, когда одно и то же значение присутствует как в отсортированном, так и во вставленном массиве, независимо от повторяющихся значений в любом из них. Повторяющиеся значения в отсортированном массиве просто преувеличивают эффект (разница между сторонами - это количество раз, когда вставляемое значение появляется в отсортированном массиве). sideэто изменить значение возвращаемого индекса, хотя он не изменяет результирующий массив из вставки значения в отсортированный массив на этих индексах. Тонкое, но важное различие; на самом деле этот ответ дает неправильный индекс, если N/2его нет aa.
askewchan
1
Как указано в приведенном выше комментарии, этот ответ не соответствует одному, если N/2его нет aa. Правильная форма будет np.searchsorted(aa, N/2, side='right')(без +1). В противном случае обе формы дают одинаковый индекс. Рассмотрим контрольный пример Nнечетности (и N/2.0принудительного смещения с плавающей запятой при использовании python 2).
askewchan
22
Меня это тоже интересовало, и я сравнил все предложенные ответы с perfplot . (Отказ от ответственности: я являюсь автором perfplot.)
Если вы знаете, что просматриваемый массив уже отсортирован , тогда
numpy.searchsorted(a, alpha)
это для вас. Это операция O (log (n)), т.е. скорость практически не зависит от размера массива. Вы не можете быть быстрее этого.
Если вы ничего не знаете о своем массиве, вы не ошибетесь с
np.searchsortedне постоянное время. На самом деле O(log(n)). Но ваш тестовый пример на самом деле сравнивает лучший вариант searchsorted(который есть O(1)).
MSeifert
@MSeifert Какой входной массив / альфа вам нужен, чтобы увидеть O (log (n))?
Нико Шлёмер
1
Получение элемента с индексом sqrt (длина) действительно привело к очень плохой производительности. Я также написал здесь ответ, включая этот тест.
MSeifert
Я сомневаюсь, что searchsorted(или любой алгоритм) может превзойти O(log(n))двоичный поиск отсортированных равномерно распределенных данных. EDIT: 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
В случае a rangeили любого другого линейно возрастающего массива вы можете просто вычислить индекс программно, без необходимости фактически перебирать массив:
deffirst_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:
return0else:
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:
return0else:
raise ValueError('no value greater than {}'.format(val))
calculated_position = (val - first_value) / step
if calculated_position < 0:
return0elif calculated_position > len(arr) - 1:
raise ValueError('no value greater than {}'.format(val))
return int(calculated_position) + 1
Возможно, это можно было бы немного улучшить. Я убедился, что он работает правильно для нескольких образцов массивов и значений, но это не значит, что там не может быть ошибок, особенно учитывая, что он использует числа с плавающей запятой ...
Учитывая, что он может вычислить позицию без какой-либо итерации, он будет постоянным time ( O(1)) и, вероятно, сможет превзойти все другие упомянутые подходы. Однако для этого требуется постоянный шаг в массиве, иначе это приведет к неправильным результатам.
Общее решение с использованием numba
Более общий подход будет использовать функцию numba:
@nb.njitdeffirst_index_numba(val, arr):for idx in range(len(arr)):
if arr[idx] > val:
return idx
return-1
Это будет работать для любого массива, но он должен перебирать массив, поэтому в среднем случае это будет O(n):
Несмотря на то, что Нико Шлёмер уже предоставил несколько тестов, я подумал, что было бы полезно включить мои новые решения и протестировать их на разные «значения».
Схема тестирования:
import numpy as np
import math
import numba as nb
deffirst_index_using_argmax(val, arr):return np.argmax(arr > val)
deffirst_index_using_where(val, arr):return np.where(arr > val)[0][0]
deffirst_index_using_nonzero(val, arr):return np.nonzero(arr > val)[0][0]
deffirst_index_using_searchsorted(val, arr):return np.searchsorted(arr, val) + 1deffirst_index_using_min(val, arr):return np.min(np.where(arr > val))
deffirst_index_calculate_range_like(val, arr):if len(arr) == 0:
raise ValueError('empty array')
elif len(arr) == 1:
if arr[0] > val:
return0else:
raise ValueError('no value greater than {}'.format(val))
first_value = arr[0]
step = arr[1] - first_value
if step <= 0:
if first_value > val:
return0else:
raise ValueError('no value greater than {}'.format(val))
calculated_position = (val - first_value) / step
if calculated_position < 0:
return0elif calculated_position > len(arr) - 1:
raise ValueError('no value greater than {}'.format(val))
return int(calculated_position) + 1@nb.njitdeffirst_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 работает потрясающе быстро, однако для массивов большего размера она уступает функции 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")
Это более интересно. Снова numba и функция вычисления работают отлично, однако на самом деле это вызывает наихудший случай сортировки по поиску, который в данном случае действительно не работает.
Сравнение функций при отсутствии значения, удовлетворяющего условию
Еще один интересный момент: как ведут себя эти функции, если нет значения, индекс которого должен быть возвращен:
arr = np.ones(100)
value = 2for 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 0is out of bounds for axis 0with 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 0is out of bounds for axis 0with size 0
Searchsorted, argmax и numba просто возвращают неверное значение. Однако searchsortedи numbaвернуть индекс, который не является допустимым индексом для массива.
Функции where, min, nonzeroи calculateбросить исключение. Однако только исключение calculateговорит что-то полезное.
Это означает, что на самом деле нужно обернуть эти вызовы в соответствующую функцию-оболочку, которая перехватывает исключения или недопустимые возвращаемые значения и обрабатывает их соответствующим образом, по крайней мере, если вы не уверены, может ли значение находиться в массиве.
Примечание. Параметры расчета и searchsortedработают только в особых условиях. Функция «вычислить» требует постоянного шага, а функция searchsorted требует сортировки массива. Таким образом, они могут быть полезны в определенных обстоятельствах, но не являются общими решениями этой проблемы. Если вы имеете дело с отсортированными списками Python, вы можете взглянуть на модуль bisect вместо использования Numpys searchsorted.
Ответы:
Это немного быстрее (и выглядит лучше)
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
источник
argmax
похоже, не останавливается на первомTrue
. (Это можно проверить, создав логические массивы с однимTrue
в разных позициях.) Скорость, вероятно, объясняется тем, чтоargmax
не нужно создавать выходной список.argmax
.aa
сортируется ли , как в ответе @ Michael).argmax
логические массивы из 10 миллионов элементов с однимTrue
в разных позициях, используя NumPy 1.11.2, и позициюTrue
значения. Таким образом,argmax
кажется, что 1.11.2 "закорачивает" логические массивы.учитывая отсортированное содержимое вашего массива, есть еще более быстрый метод: 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
источник
+1
np.searchsorted(..., side='right')
side
аргумент имеет значение только в том случае, если в отсортированном массиве есть повторяющиеся значения. Это не меняет значения возвращаемого индекса, который всегда является индексом, в который вы можете вставить значение запроса, смещая все следующие записи вправо и сохраняя отсортированный массив.side
действует, когда одно и то же значение присутствует как в отсортированном, так и во вставленном массиве, независимо от повторяющихся значений в любом из них. Повторяющиеся значения в отсортированном массиве просто преувеличивают эффект (разница между сторонами - это количество раз, когда вставляемое значение появляется в отсортированном массиве).side
это изменить значение возвращаемого индекса, хотя он не изменяет результирующий массив из вставки значения в отсортированный массив на этих индексах. Тонкое, но важное различие; на самом деле этот ответ дает неправильный индекс, еслиN/2
его нетaa
.N/2
его нетaa
. Правильная форма будетnp.searchsorted(aa, N/2, side='right')
(без+1
). В противном случае обе формы дают одинаковый индекс. Рассмотрим контрольный примерN
нечетности (иN/2.0
принудительного смещения с плавающей запятой при использовании python 2).Меня это тоже интересовало, и я сравнил все предложенные ответы с perfplot . (Отказ от ответственности: я являюсь автором perfplot.)
Если вы знаете, что просматриваемый массив уже отсортирован , тогда
это для вас. Это операция O (log (n)), т.е. скорость практически не зависит от размера массива. Вы не можете быть быстрее этого.
Если вы ничего не знаете о своем массиве, вы не ошибетесь с
Уже отсортировано:
Несортированный:
Код для воспроизведения сюжета:
import numpy import perfplot alpha = 0.5 numpy.random.seed(0) 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) perfplot.save( "out.png", # 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, 23)], xlabel="len(array)", )
источник
np.searchsorted
не постоянное время. На самом делеO(log(n))
. Но ваш тестовый пример на самом деле сравнивает лучший вариантsearchsorted
(который естьO(1)
).searchsorted
(или любой алгоритм) может превзойтиO(log(n))
двоичный поиск отсортированных равномерно распределенных данных. EDIT:searchsorted
это двоичный поиск.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
источник
Массивы с постоянным шагом между элементами
В случае 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
Учитывая, что он может вычислить позицию без какой-либо итерации, он будет постоянным time (
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
Контрольный показатель
Несмотря на то, что Нико Шлёмер уже предоставил несколько тестов, я подумал, что было бы полезно включить мои новые решения и протестировать их на разные «значения».
Схема тестирования:
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
и графики были созданы с использованием:
элемент находится в начале
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 работает потрясающе быстро, однако для массивов большего размера она уступает функции 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")
Это более интересно. Снова 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 просто возвращают неверное значение. Однако
searchsorted
иnumba
вернуть индекс, который не является допустимым индексом для массива.Функции
where
,min
,nonzero
иcalculate
бросить исключение. Однако только исключениеcalculate
говорит что-то полезное.Это означает, что на самом деле нужно обернуть эти вызовы в соответствующую функцию-оболочку, которая перехватывает исключения или недопустимые возвращаемые значения и обрабатывает их соответствующим образом, по крайней мере, если вы не уверены, может ли значение находиться в массиве.
Примечание. Параметры расчета и
searchsorted
работают только в особых условиях. Функция «вычислить» требует постоянного шага, а функция searchsorted требует сортировки массива. Таким образом, они могут быть полезны в определенных обстоятельствах, но не являются общими решениями этой проблемы. Если вы имеете дело с отсортированными списками Python, вы можете взглянуть на модуль bisect вместо использования Numpys searchsorted.источник
Я хотел бы предложить
np.min(np.append(np.where(aa>5)[0],np.inf))
Это вернет наименьший индекс, в котором выполняется условие, и бесконечность, если условие никогда не выполняется (и
where
возвращает пустой массив).источник
Я бы пошел с
где
V
- вектор (1d массив),x
- значение иi
- результирующий индекс.источник