在numpy数组中计算长度可变的连续值出现次数

28

假设我有一个包含一系列数字的numpy数组,我根据条件对它们进行测试并返回一个布尔数组:

np.random.seed(3456)
a = np.random.rand(8)
condition = a>0.5

使用这个布尔数组,我想计算连续出现True的所有长度。例如,如果我有[True,True,True,False,False,True,True,False,True],我想要得到[3,2,1]

我可以使用以下代码实现:

length,count = [],0
for i in range(len(condition)):

    if condition[i]==True:
        count += 1
    elif condition[i]==False and count>0:
        length.append(count)
        count = 0

    if i==len(condition)-1 and count>0:
        length.append(count)

    print length

但是是否已经有任何已实现的东西或Python、numpy、scipy等函数可以计算列表或数组中给定输入的连续出现次数的长度?


可能是[Numpy grouping using itertools.groupby performance]的重复问题 (https://dev59.com/w2445IYBdhLWcg3w4-Be) - simonzack
5个回答

55

如果您已经有一个numpy数组,那么这个方法可能会更快:

>>> condition = np.array([True,True,True,False,False,True,True,False,True])
>>> np.diff(np.where(np.concatenate(([condition[0]],
                                     condition[:-1] != condition[1:],
                                     [True])))[0])[::2]
array([3, 2, 1])

它可以检测块的起始位置,对于第一个和最后一个块有一些逻辑,并且仅计算块开始之间的差异,并且放弃与False块相对应的长度。


我将此函数传递给pandas数据框的groupby和resample方法,因此我猜最终会得到一个numpy数组。在这种情况下,速度并不是很重要,但在处理更大的数据集时需要注意。 - pbreach
我发现对于1e6个布尔值,这种方法比itertools方法快了几个数量级。谢谢! - sfjac
不错,谢谢!您是否有建议,我如何调整您的代码,以便我可以在2D numpy数组上逐行执行此操作? - pr94
1
@pr94 如果按行执行,每个答案的数组长度可能会不同。我猜你需要逐行执行,因此只需循环一些操作,并添加额外的索引 [0,:] 而不是 [0] - goryh

22

这里有一个使用itertools的解决方案(可能不是最快的解决方案):

import itertools
condition = [True,True,True,False,False,True,True,False,True]
[ sum( 1 for _ in group ) for key, group in itertools.groupby( condition ) if key ]

Out:
[3, 2, 1]

绝对是一个非常Pythonic的答案!实际上,这比我之前的代码片段要快得多,大约0.2秒,而不是1-2秒。 - pbreach
它起初是可以运行的...但后来开始显示这个错误:数组的真值不明确。毫无预兆,不知道为什么。在IDLE中可以运行,但在PyCharm中却不能。 - Piotr Kamoda
1
如果您使用len(list(group))而不是sum(1 ...)则会稍微快一些,但如果您已经有一个numpy数组,则仍然比@Jaime的答案慢得多。 - sfjac

3
你还可以通过查看条件数组的反转的指数(即np.where的结果)来计算连续False值之间的距离。关键是确保布尔数组以False开头。基本上,你正在计算在True条件之间的边界之间的距离。
condition = np.array([True, True, True, False, False, True, True, False, True, False])
if condition[0]:
    condition = np.concatenate([[False], condition])

idx = np.where(~condition)[0]

在最后一步,您需要从这些值中减去1,以便删除左侧和右侧的边缘。
>>> np.ediff1d(idx) - 1
array([3, 0, 2, 1])

2
np.unique((~arr).cumsum()[arr], return_counts=True)[1]

0
如果t是np数组并且按升序排序,则:
d=np.diff(t)
d_incr = np.argwhere(d>0).flatten()
d_incr = np.insert(d_incr, 0, 0)

np数组d_incr将包含发生更改的索引,允许对d_incr [i-1]和d_incr [i]之间的值组执行操作,其中i的范围为(1,d_incr.size)


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