在python

时间:2019-01-17 13:43:14

标签: python algorithm numpy

给出一个由个排序的值组成的数组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)

但是我觉得必须有一种更有效的方法来做到这一点?

3 个答案:

答案 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]

alt的工作原理:

生成垃圾箱的起始值和结束值:

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_startsbin_endsa中每个值都适合的地方:

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_counta的性能:

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)')

enter image description here

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循环的长度。