Numpy sum 运行 非零值的长度

Numpy sum running length of non-zero values

正在寻找 return 连续非零值的滚动数的快速向量化函数。每当遇到零时,计数应从 0 开始。结果应与输入数组具有相同的形状。

给定一个这样的数组:

x = np.array([2.3, 1.2, 4.1 , 0.0, 0.0, 5.3, 0, 1.2, 3.1])

函数应该return这个:

array([1, 2, 3, 0, 0, 1, 0, 1, 2])

您可以使用 itertools.groupbynp.hstack :

>>> import numpy as np
>>> x = np.array([2.3, 1.2, 4.1 , 0.0, 0.0, 5.3, 0, 1.2, 3.1])
>>> from itertools import groupby

>>> np.hstack([[i if j!=0 else j for i,j in enumerate(g,1)] for _,g in groupby(x,key=lambda x: x!=0)])
array([ 1.,  2.,  3.,  0.,  0.,  1.,  0.,  1.,  2.])

我们可以根据非零元素对数组元素进行分组,然后使用列表理解和枚举来用这些索引替换非零子数组,然后用 np.hstack 展平列表。

此 post 列出了一个矢量化方法,它基本上由两个步骤组成:

  1. 初始化一个与输入向量 x 大小相同的零向量,并在 x.

  2. 的非零对应的位置设置一个
  3. 接下来,在该向量中,我们需要在每个 "island" 的 ending/stop 位置之后将每个岛的长度减去 运行。目的是稍后再次使用 cumsum,这将导致 "islands" 的序号和其他地方的零。

下面是实现-

import numpy as np

#Append zeros at the start and end of input array, x
xa = np.hstack([[0],x,[0]])

# Get an array of ones and zeros, with ones for nonzeros of x and zeros elsewhere
xa1 =(xa!=0)+0

# Find consecutive differences on xa1
xadf = np.diff(xa1)

# Find start and stop+1 indices and thus the lengths of "islands" of non-zeros
starts = np.where(xadf==1)[0]
stops_p1 = np.where(xadf==-1)[0]
lens = stops_p1 - starts

# Mark indices where "minus ones" are to be put for applying cumsum
put_m1 = stops_p1[[stops_p1 < x.size]]

# Setup vector with ones for nonzero x's, "minus lens" at stops +1 & zeros elsewhere
vec = xa1[1:-1] # Note: this will change xa1, but it's okay as not needed anymore
vec[put_m1] = -lens[0:put_m1.size]

# Perform cumsum to get the desired output
out = vec.cumsum()

样本运行-

In [116]: x
Out[116]: array([ 0. ,  2.3,  1.2,  4.1,  0. ,  0. ,  5.3,  0. ,  1.2,  3.1,  0. ])

In [117]: out
Out[117]: array([0, 1, 2, 3, 0, 0, 1, 0, 1, 2, 0], dtype=int32)

运行时测试 -

这里有一些 运行 次测试,将提议的方法与其他方法进行比较 -

In [21]: N = 1000000
    ...: x = np.random.rand(1,N)
    ...: x[x>0.5] = 0.0
    ...: x = x.ravel()
    ...: 

In [19]: %timeit sumrunlen_vectorized(x)
10 loops, best of 3: 19.9 ms per loop

In [20]: %timeit sumrunlen_loopy(x)
1 loops, best of 3: 2.86 s per loop

我在 Kick Start 2021 轮 A 中遇到了这个子问题。我的解决方案:

def current_run_len(a):
    a_ = np.hstack([0, a != 0, 0])  # first in starts and last in stops defined
    d = np.diff(a_)
    starts = np.where(d == 1)[0]
    stops = np.where(d == -1)[0]
    a_[stops + 1] = -(stops - starts)  # +1 for behind-last
    return a_[1:-1].cumsum()

事实上,该问题还需要一个版本,其中您计算 向下 个连续序列。因此这里有另一个带有可选关键字参数的版本,它对 rev=False:

做同样的事情
def current_run_len(a, rev=False):
    a_ = np.hstack([0, a != 0, 0])  # first in starts and last in stops defined
    d = np.diff(a_)
    starts = np.where(d == 1)[0]
    stops = np.where(d == -1)[0]
    if rev:
        a_[starts] = -(stops - starts)
        cs = -a_.cumsum()[:-2]
    else:
        a_[stops + 1] = -(stops - starts)  # +1 for behind-last
        cs = a_.cumsum()[1:-1]
    return cs

结果:

a = np.array([1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1])
print('a                             = ', a)
print('current_run_len(a)            = ', current_run_len(a))
print('current_run_len(a, rev=True)  = ', current_run_len(a, rev=True))
a                             =  [1 1 1 1 0 0 0 1 1 0 1 0 0 0 1]
current_run_len(a)            =  [1 2 3 4 0 0 0 1 2 0 1 0 0 0 1]
current_run_len(a, rev=True)  =  [4 3 2 1 0 0 0 2 1 0 1 0 0 0 1]

对于只由0和1组成的数组,可以将[0, a != 0, 0]化简为[0, a, 0]。但是发布的版本也适用于任意非零数字。