假设我有一个NumPy整数数组。
arr = np.random.randint(0, 1000, 1000)
我有两个数组lower
和upper
,它们分别代表arr
切片的下限和上限。这些区间是重叠且可变长度的,但lowers
和uppers
都保证不会减少。
lowers = np.array([0, 5, 132, 358, 566, 822])
uppers = np.array([45, 93, 189, 533, 800, 923])
我想找到由arr
和lowers
定义的每个uppers
切片的最小值和最大值,并将它们存储在另一个数组中。
out_arr = np.empty((lowers.size, 2))
最有效的方法是什么?我担心没有矢量化方法,因为我无法看到我如何绕过循环索引..
我目前的做法只是直截了当的
for i in range(lowers.size):
arr_v = arr[lowers[i]:uppers[i]]
out_arr[i,0] = np.amin(arr_v)
out_arr[i,1] = np.amax(arr_v)
让我得到了想要的结果,如
In [304]: out_arr
Out[304]:
array([[ 26., 908.],
[ 18., 993.],
[ 0., 968.],
[ 3., 999.],
[ 1., 998.],
[ 0., 994.]])
但这对我的实际数据来说太慢了。
答案 0 :(得分:3)
好的,以下是使用np.minimum.reduceat
至少缩小原始问题的方法:
lu = np.r_[lowers, uppers]
so = np.argsort(lu)
iso = np.empty_like(so)
iso[so] = np.arange(len(so))
cut = len(lowers)
lmin = np.minimum.reduceat(arr, lu[so])
for i in range(cut):
print(min(lmin[iso[i]:iso[cut+i]]), min(arr[lowers[i]:uppers[i]]))
# 33 33
# 7 7
# 5 5
# 0 0
# 3 3
# 7 7
这没有实现的是摆脱主循环,但至少数据从1000个元素数组减少到12个元素数组。
更新
小重叠@Eric Hansen自己的解决方案很难被击败。我还是想指出,如果存在重大的重叠,那么将两种方法结合起来甚至是值得的。我没有numba
,所以下面只是一个twopass版本,它结合了我的预交叉与Eric的纯numpy
解决方案,该解决方案也以{{1}的形式作为基准}}:
onepass
示例运行:
import numpy as np
from timeit import timeit
def twopass(lowers, uppers, arr):
lu = np.r_[lowers, uppers]
so = np.argsort(lu)
iso = np.empty_like(so)
iso[so] = np.arange(len(so))
cut = len(lowers)
lmin = np.minimum.reduceat(arr, lu[so])
return np.minimum.reduceat(lmin, iso.reshape(2,-1).T.ravel())[::2]
def onepass(lowers, uppers, arr):
mixture = np.empty((lowers.size*2,), dtype=lowers.dtype)
mixture[::2] = lowers; mixture[1::2] = uppers
return np.minimum.reduceat(arr, mixture)[::2]
arr = np.random.randint(0, 1000, 1000)
lowers = np.array([0, 5, 132, 358, 566, 822])
uppers = np.array([45, 93, 189, 533, 800, 923])
print('small')
for f in twopass, onepass:
print('{:18s} {:9.6f} ms'.format(f.__name__,
timeit(lambda: f(lowers, uppers, arr),
number=10)*100))
arr = np.random.randint(0, 1000, 10**6)
lowers = np.random.randint(0, 8*10**5, 10**4)
uppers = np.random.randint(2*10**5, 10**6, 10**4)
swap = lowers > uppers
lowers[swap], uppers[swap] = uppers[swap], lowers[swap]
print('large')
for f in twopass, onepass:
print('{:18s} {:10.4f} ms'.format(f.__name__,
timeit(lambda: f(lowers, uppers, arr),
number=10)*100))
答案 1 :(得分:1)
根据Paul Panzer对reduceat
的建议,我提出的原始尝试的改进版本是
mixture = np.empty((lowers.size*2,), dtype=lowers.dtype)
mixture[::2] = lowers; mixture[1::2] = uppers
np.column_stack((np.minimum.reduceat(arr, mixture)[::2],
np.maximum.reduceat(arr, mixture)[::2]))
在与我的实际数据相当的样本大小上,我的机器运行时间为4.22毫秒,而原始解决方案需要73毫秒。
更快的是使用Numba和原始解决方案
from numba import jit
@jit
def get_res():
out_arr = np.empty((lowers.size, 2))
for i in range(lowers.size):
arr_v = arr[lowers[i]:uppers[i]]
out_arr[i,0] = np.amin(arr_v)
out_arr[i,1] = np.amax(arr_v)
return out_arr
在我的机器上运行100微秒。
答案 2 :(得分:-1)
执行速度很慢,因为在循环内部,子数组被复制到数组中,然后执行操作。您可以通过单行代码来避免整个循环
out_array = np.array([(np.amin(arr[lowers[i]:uppers[i]]),np.amax(arr[lowers[i]:uppers[i]])) for i in range(lowers.size)])