如何在Numpy数组中计算每个1左侧的零的数量

4

我有一个类似这样的numpy二进制数组:

   Array A = [1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0]

我想要计算每个1左侧的0的数量,并将其返回到另一个数组中。对于这个例子,返回的数组应该像这样:
nb_1s = [0, 0, 1, 2, 2, 5]

这个数组的前两个数字没有左边的0,因此它们是0等等...

我知道首先我必须用我的数组中的1的数量初始化一个数组:

def give_zeros(binary_array):
    binary_array = np.asarray(binary_array)
    nb_zeros = np.zeros(binary_array.sum())


    return nb_zeros

但是我不确定如何计算零的数量。我应该使用'nditer'在循环中迭代吗?这似乎不太高效,因为我将不得不在非常大的数组上运行此函数。

你有什么想法吗? 谢谢。


这不应该是:[0, 0, 1, 1, 0, 3]吗? - Divakar
计数是累积的吗? - alvas
是的,计数是累积的,最后一个1左侧有3个零,再加上1后面的2个零。 - user2505650
4个回答

4

代码

您可以使用以下方法:

(A == 0).cumsum()[A > 0]
# array([0, 0, 1, 2, 2, 5])

或者:

(~A).cumsum()[A]
# array([0, 0, 1, 2, 2, 5])

如果A是一个布尔数组。

解释

A == 0是一个布尔数组,对于每个0都是True

>>> import numpy as np
>>> A = np.array([1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0])
>>> A == 0
array([False, False,  True, False,  True, False, False,  True,  True,
        True, False,  True,  True,  True,  True], dtype=bool)

您可以使用 cumsum() 来计算 True 的数量:

>>> (A == 0).cumsum()
array([0, 0, 1, 1, 2, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9])

您只需要那些 A > 0 的值:

>>> (A == 0).cumsum()[A > 0]
array([0, 0, 1, 2, 2, 5])

完成!


1
这似乎是最符合Python风格的解决方案,即使不是最高效的。 - Daniel F
@DanielF:谢谢。确实,被采纳的答案比我的代码快20%。 - Eric Duminil
1
通常情况下,有错误的方法、正确的方法和@Divakar的方法——这种方法有点像正确的方法,但是需要更多的代码,但不知何故更快。 - Daniel F
1
@EricDuminil 在我的帖子中添加了更大数据集的时间。使用布尔数组转换来提高flatnonzero的处理性能。现在相比于你的代码,速度提升了约 3倍 - Divakar

3
这里有一种向量化的方法,可以通过从索引中的1s区间数组进行差分来实现 -
def leftzeros_count(a):
    idx = np.flatnonzero(a!=0)
    return idx - np.arange(len(idx))

示例运行 -


In [298]: a = np.array([1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0])

In [299]: leftzeros_count(a)
Out[299]: array([0, 0, 1, 2, 2, 5])

In [300]: a = np.array([0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0])

In [301]: leftzeros_count(a)
Out[301]: array([1, 1, 2, 3, 3, 6])

In [302]: a = np.array([0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1])

In [303]: leftzeros_count(a)
Out[303]: array([ 1,  1,  2,  3,  3,  6, 10])

运行时测试

为了计时,让我们将给定的示例大量复制并测试向量化方法 -

In [7]: a = np.array([1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0])

In [8]: a = np.tile(a,100000)

# @Eric Duminil's soln
In [9]: %timeit (a == 0).cumsum()[a > 0]
100 loops, best of 3: 10.9 ms per loop

# Proposed in this post
In [10]: %timeit leftzeros_count(a)
100 loops, best of 3: 3.71 ms per loop

谢谢您的回答,我尝试了这个数组:[1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0],但是函数返回了:[2, 4, 4, 4, 8, 10],它应该是:[0, 2, 2, 2, 6, 8]。 - user2505650
@user2505650 请查看修改。我已经更新了一个新的方法。 - Divakar
最好直接使用"非零元素"而不是在调用ravel之前使用flatnonzero - alvas

2

非向量化方式:

>>> x = [1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0]
>>> c, y = 0, []
>>> for i in x:
...     if i == 1:
...         y.append(c)
...     else:
...         c += 1
... 
>>> y
[0, 0, 1, 2, 2, 5]

对于向量化解决方案,请参见@Divakar的答案:

numpy中,首先使用np.nonzero()找到非零索引:

>>> np.nonzero(x)[0]
array([ 0,  1,  3,  5,  6, 10])

然后,将其减去索引长度的范围数组:

>>> idx = np.nonzero(x)[0]
>>> np.arange(len(idx))
array([0, 1, 2, 3, 4, 5])
>>> np.nonzero(x)[0] - np.arange(len(idx))
array([0, 0, 1, 2, 2, 5])

>>> np.arange(x.count(1))
array([0, 1, 2, 3, 4, 5])
>>> np.nonzero(x)[0] - np.arange(x.count(1))
array([0, 0, 1, 2, 2, 5])

是的,不过不是numpy。 - cs95
这跟我的解决方案有什么不同? - Divakar
@Divakar,没有太大的区别,只是flatnonzero多了一个不必要的ravel()步骤;P - alvas
@alvas,就内存或性能而言,这是微不足道的差异。那么重要的区别在哪里呢? - Divakar
没有区别...只是我认为将向量化和非向量化的答案并置很好...我会添加编辑注释...还在打字中... - alvas

1
如果计数是累积的(例如您的示例),那么您可以轻松地使用O(n)完成此操作。只需拥有一个计数器,每当您找到零时就将其增加一次,然后对于您初始数组中遇到的每个一,将计数器变量的值附加到另一个数组中即可。

@EricDuminil,虽然您提供了简洁的解决方案,但我不赞成为像这样简单的问题提供直接的代码解决方案。这会进一步促进复制和粘贴编程,污染全球各地的代码库。这只是我的个人看法(显然是基于观点的)。 - Sebastian Fleck
1
感谢评论。我理解你的观点。我非常欣赏NumPy简洁的语法,无法抗拒写一些东西。 - Eric Duminil

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接