【发布时间】:2019-03-31 22:11:44
【问题描述】:
我需要在满足条件的一维 NumPy 数组或 Pandas 数字系列中找到第一个值的索引。数组很大,索引可能靠近数组的开始或结束,或条件可能根本不满足。我无法提前判断哪个更有可能。如果不满足条件,则返回值应为-1。我考虑了几种方法。
尝试 1
# func(arr) returns a Boolean array
idx = next(iter(np.where(func(arr))[0]), -1)
但这通常太慢了,因为func(arr) 在 整个 数组上应用了矢量化函数,而不是在满足条件时停止。具体来说,当条件在数组的开始附近满足时,它是昂贵的。
尝试 2
np.argmax 稍微快一些,但无法识别何时从未满足条件:
np.random.seed(0)
arr = np.random.rand(10**7)
assert next(iter(np.where(arr > 0.999999)[0]), -1) == np.argmax(arr > 0.999999)
%timeit next(iter(np.where(arr > 0.999999)[0]), -1) # 21.2 ms
%timeit np.argmax(arr > 0.999999) # 17.7 ms
np.argmax(arr > 1.0) 返回0,即条件不满足时的实例。
尝试 3
# func(arr) returns a Boolean scalar
idx = next((idx for idx, val in enumerate(arr) if func(arr)), -1)
但是当在数组的end附近满足条件时,这太慢了。这可能是因为生成器表达式因大量__next__ 调用而产生了昂贵的开销。
这总是是一种妥协还是有办法,对于通用func,有效地提取第一个索引?
基准测试
对于基准测试,假设func 在值大于给定常数时找到索引:
# Python 3.6.5, NumPy 1.14.3, Numba 0.38.0
import numpy as np
np.random.seed(0)
arr = np.random.rand(10**7)
m = 0.9
n = 0.999999
# Start of array benchmark
%timeit next(iter(np.where(arr > m)[0]), -1) # 43.5 ms
%timeit next((idx for idx, val in enumerate(arr) if val > m), -1) # 2.5 µs
# End of array benchmark
%timeit next(iter(np.where(arr > n)[0]), -1) # 21.4 ms
%timeit next((idx for idx, val in enumerate(arr) if val > n), -1) # 39.2 ms
【问题讨论】:
标签: python arrays pandas performance numpy