我在numpy中有一个一维数组,想要找到某个值超过numpy数组中的值的索引位置。
例如:
aa = range(-10,10)
查找在 aa
中,第一次出现大于 5
的数的位置。
我在numpy中有一个一维数组,想要找到某个值超过numpy数组中的值的索引位置。
例如:
aa = range(-10,10)
查找在 aa
中,第一次出现大于 5
的数的位置。
这会快一点(而且看起来更好)
np.argmax(aa>5)
argmax
会在第一个True
处停止("In case of multiple occurrences of the maximum values, the indices corresponding to the first occurrence are returned."),并且不会保存另一个列表。
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
不需要创建输出列表所解释的。 - DrVargmax
的内部细节。 - askewchanargmax
函数,这些数组在不同位置只有一个True
,但是True
所在的位置会影响结果。因此,似乎1.11.2版本的argmax
在处理布尔数组时存在"短路"现象。 - Ulrich Stern如果你的数组已经排序,那么有一种更快的方法: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
np.searchsorted(..., side='right')
避免笨拙的+1
。 - askewchanside
参数才会起到作用。它不会改变返回的索引的含义,该索引始终是插入查询值并将所有后续条目向右移动以保持有序数组的索引位置。请记住,不要改变原文意思,并尽可能使翻译通俗易懂。 - GusN/2
不在aa
中,则此答案会偏差1。正确的形式应该是np.searchsorted(aa, N/2, side='right')
(没有+1
)。否则,两种形式都会给出相同的索引。考虑N
为奇数的测试用例(如果使用python 2,请将N/2.0
强制转换为浮点数)。 - askewchan我对此也很感兴趣,我已经将所有建议的答案与 perfplot 进行了比较。(免责声明:我是 perfplot 的作者。)
如果您知道您正在查找的数组 已经排序,那么
numpy.searchsorted(a, alpha)
这对你来说非常有用。它是O(log(n))操作,也就是说,速度几乎不取决于数组的大小。你无法比这更快。
如果您不知道有关数组的任何信息,则不会出错
numpy.argmax(a > alpha)
已排序:
未排序:
生成图表的代码:
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)
)。 - MSeifertsearchsorted
(或任何算法)能否击败二分查找对于已排序的均匀分布数据的O(log(n))
。编辑:searchsorted
是一个二分查找。 - Mateen UlhaqIn [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
对于一个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函数:
@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函数表现最佳,其次是calculate函数和searchsorted函数。其他解决方案的表现要差得多。
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函数和searchsorted函数超越。
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和calculate函数表现出色,但是在这种情况下,实际上会触发searchsorted的最坏情况,这种情况下不起作用。
另一个有趣的点是,如果没有任何一个值的索引应该被返回,这些函数将如何行动:
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
的异常实际上提供了有用的信息。
这意味着如果你不确定值是否在数组中,你需要将这些调用包装在适当的包装函数中,以捕获异常或无效的返回值,并进行适当的处理。
我想提议
np.min(np.append(np.where(aa>5)[0],np.inf))
这将返回满足条件的最小索引,如果条件从未满足,则返回无穷大(where
返回一个空数组)。
np.where
而不是np.argmax
。后者即使没有找到值,也会返回位置0,这不是你期望的索引。>>> aa = np.array(range(-10,10))
>>> print(aa)
array([-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2,
3, 4, 5, 6, 7, 8, 9])
>>> idx = np.where(aa > 5)[0]
>>> print(idx)
array([16, 17, 18, 19], dtype=int64)
>>> not_found = len(np.where(aa > 20)[0])
>>> print(not_found)
array([], dtype=int64)
argmax
的观点是:如果解决方案不含糊,那么越简单越好。因此,要检查某些内容是否符合条件,只需执行 if len(np.where(aa > value_to_search)[0]) > 0
。我会选择
i = np.min(np.where(V >= x))
其中V
是向量(1维数组),x
是值,i
是结果索引。
np.where(capacity < demand)[0][0]
慢。除了可读性更好的 np.min
之外,没有使用它的理由。 - Muhammad Yasirroni