给出一个由个排序的值组成的数组a
和一个范围为bins
的范围数组,最有效的方法是计算{{ 1}}是否在a
的每个范围rng
中?
当前我正在执行以下操作:
bins
哪个返回预期的数组
def sliding_count(a, end, window, start=0, step=1):
bins = [(x, x + window) for x in range(start, (end + 1) - window, step)]
counts = np.zeros(len(bins))
for i, rng in enumerate(bins):
count = len(a[np.where(np.logical_and(a>=rng[0], a<=rng[1]))])
counts[i] = count
return counts
a = np.array([1, 5, 8, 11, 14, 19])
end = 20
window = 10
sliding_count(a, end, window)
但是我觉得必须有一种更有效的方法来做到这一点?
答案 0 :(得分:3)
import numpy as np
def alt(a, end, window, start=0, step=1):
bin_starts = np.arange(start, end+1-window, step)
bin_ends = bin_starts + window
last_index = np.searchsorted(a, bin_ends, side='right')
first_index = np.searchsorted(a, bin_starts, side='left')
return last_index - first_index
def sliding_count(a, end, window, start=0, step=1):
bins = [(x, x + window) for x in range(start, (end + 1) - window, step)]
counts = np.zeros(len(bins))
for i, rng in enumerate(bins):
count = len(a[np.where(np.logical_and(a>=rng[0], a<=rng[1]))])
counts[i] = count
return counts
a = np.array([1, 5, 8, 11, 14, 19])
end = 20
window = 10
print(sliding_count(a, end, window))
# [3. 4. 3. 3. 4. 4. 3. 3. 3. 3. 3.]
print(alt(a, end, window))
# [3 4 3 3 4 4 3 3 3 3 3]
生成垃圾箱的起始值和结束值:
In [73]: bin_starts = np.arange(start, end+1-window, step); bin_starts
Out[73]: array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
In [74]: bin_ends = bin_starts + window; bin_ends
Out[74]: array([10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20])
由于a
已排序,因此您可以使用np.searchsorted
查找第一个和最后一个索引
bin_starts
和bin_ends
中a
中每个值都适合的地方:
In [75]: last_index = np.searchsorted(a, bin_ends, side='right'); last_index
Out[75]: array([3, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6])
In [76]: first_index = np.searchsorted(a, bin_starts, side='left'); first_index
Out[76]: array([0, 0, 1, 1, 1, 1, 2, 2, 2, 3, 3])
count
只是索引的区别:
In [77]: last_index - first_index
Out[77]: array([3, 4, 3, 3, 4, 4, 3, 3, 3, 3, 3])
这是perfplot,它根据alt
的长度来比较sliding_count
和a
的性能:
import perfplot
def make_array(N):
a = np.random.randint(10, size=N)
a = a.cumsum()
return a
def using_sliding(a):
return sliding_count(a, end, window)
def using_alt(a):
return alt(a, end, window)
perfplot.show(
setup=make_array,
kernels=[using_sliding, using_alt],
n_range=[2**k for k in range(22)],
logx=True,
logy=True,
xlabel='len(a)')
Perfplot还检查using_sliding
返回的值是否等于using_alt
返回的值。
Matt Timmermans' idea,“ 从该容器的计数中减去position_in_a
”触发了此解决方案。
答案 1 :(得分:1)
bin b
中的元素数是元素<= b.end
减去元素数< b.start
的数量。
因此,您可以将starts
的bin数组按开始排序,并将ends
的bin数组按结束排序。然后逐步浏览所有3个数组。当您越过x
中的每个a
时,越过起始点x < b.start
并从该容器的计数中减去 position_in_a
。然后使用x <= b.end
和 add position_in_a
前进到该容器的计数结束。
总复杂度为O(N log N),主要由对开始和结束数组进行排序决定。遍历这3个数组并调整计数是O(N)。
在您的代码中,您正在生成已排序的垃圾箱数组,因此,如果可以这样做,则可以跳过排序步骤,总复杂度为O(a.length + bin_count)。我什至不愿意生成该数组,因为您可以轻松地从索引中计算起始值和结束值。
答案 2 :(得分:0)
类似这样的东西(?):
def sliding_count(a, nx0, nx1, window):
bin0 = np.arange(nx0,nx1,1)
bin1 = bin0 + window
count = np.zeros((nx1-nx0), dtype=int)
for j in range(nx1-nx0):
count[j] = np.sum(a<=bin1[j]) - np.sum(a<bin0[j])
return count
#---- main ---------------
nx0, nx1, window = 0, 11, 10
a = np.array([1, 5, 8, 11, 14, 19])
sliding_count(a, nx0, nx1, window)
array([3, 4, 3, 3, 4, 4, 3, 3, 3, 3, 3])
我没有检查 bin0 = np.arange(nx0,nx1,1) nx0> 0 和 step> 1 的代码>。因此,对于这种情况,必须修改for循环的长度。