【问题标题】:Numpy sum running length of non-zero values非零值的numpy总和运行长度
【发布时间】:2015-07-04 13:31:22
【问题描述】:

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

给定一个这样的数组:

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

函数应该返回这个:

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

【问题讨论】:

    标签: python arrays performance numpy vectorization


    【解决方案1】:

    这篇文章列出了一种矢量化方法,它基本上包括两个步骤:

    1. 初始化与输入向量 x 大小相同的零向量,并在与 x 的非零对应的位置设置 1。

    2. 接下来,在那个向量中,我们需要在每个“岛”的结束/停止位置之后加上每个岛的游程长度的减值。其目的是稍后再次使用 cumsum,这将导致“岛屿”的连续编号和其他地方的零。

    这是实现 -

    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)
    

    运行时测试 -

    以下是一些运行时测试,将建议的方法与其他 itertools.groupby based approach 进行比较 -

    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
    

    【讨论】:

    • 我在考虑这些思路,但无法确定如何计算过渡点处的负游程长度。感谢您的解决方案。它可以通过删除 vec1 来缩短一点,因为 vec1.cumsum() 等于 (x != 0) + 0。我是新来的。不确定是否可以编辑其他人的答案。
    • @steve 啊是的,很好的观察!让我编辑它,因为我还需要编辑解释这些步骤的文本。
    • 再多一点... non_zero = (x != 0) + 0; xa = np.hstack([[0],non_zero,[0]]); xadf = np.diff(xa);然后是 vec2 = non_zero
    • @steve 在我对xadf 做类似的事情进行了编辑之后,我也有同样的想法。让我用这些优化来编辑它。
    • @steve 编辑现在应该涵盖这些优化。
    【解决方案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 展平列表。

    【讨论】:

    • 正如提到的另一个答案,这个解决方案对于大输入来说很慢,因为它是纯 Python 而不是基于 numpy。
    【解决方案3】:

    我在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]。但发布的版本也适用于任意非零数字。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-21
      • 2017-01-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-12
      • 2010-11-07
      相关资源
      最近更新 更多