繁体   English   中英

在一维 numpy 数组中分组连续 True

[英]Group consecutive True in 1-D numpy array

假设我们有一个 boolean 数组x=np.array([True, True, False, True, False]) 有两组连续的True 我想要的是创建 boolean arrays l的列表,其中l中的每个数组只包含一组连续的True 例如, x应该与y相同,定义为

y = np.zeros_like(x)
for e in l:
    y = y|e

到目前为止,我唯一成功的尝试是使用consecutive function by https://stackoverflow.com/a/7353335/4755229

def consecutive_bools(bool_input):
    consecutive_idx = consecutive(np.argwhere(bool_input).flatten())
    ret = [np.zeros_like(bool_input) for i in range(len(consecutive_idx))]
    for i, idx in enumerate(consecutive_idx):
        ret[i][idx] = True
    return ret

这似乎过于复杂。 有没有更好(简洁,可能更快)的方法来做到这一点?

考虑以下:

import numpy as np

x=np.array([True, True, False, True, False])

idx, = np.where(np.insert(x,0,False) ^ np.insert(x,-1,False))

l = [np.zeros_like(x),np.zeros_like(x)]
l[0][idx[0]:idx[1]] = True
l[1][idx[2]:idx[3]] = True

这里的想法是 idx 的元素是从 True 到 False 或从 True 到 False 的任何开关的索引。 由于您正好有 2 个连续的True组,因此 idx 正好有 4 个元素。

对于任意数量的连续组:

idx, = np.where(np.insert(x,0,False) ^ np.insert(x,-1,False))

l = [np.zeros_like(x) for _ in range(len(idx)//2)]
for a,p in zip(l,np.split(idx,np.arange(2,len(idx),2))):
    a[slice(*p)] = True

一个有趣的方法是构造每个段的起点和终点,然后通过np.arange(x.size)构造一个数组。 比较它和所有以>=开头的,并比较它和所有以<结尾的。 两个结果的逻辑与产生所需的 output:

def my_consecutive_bools(ar):
    indices, = np.concatenate([ar[:1], ar[:-1] != ar[1:], ar[-1:]]).nonzero()
    arange = np.arange(ar.size)
    return np.logical_and(arange >= indices[::2, None],
                          arange < indices[1::2, None])
>>> x = np.array([True, True, False, True, False])
>>> my_consecutive_bools(x)
array([[ True,  True, False, False, False],
       [False, False, False,  True, False]])

这种方法在一些小的arrays上效果很好,但是时间复杂度高。 对于较大的 arrays,您可以简单地遍历 start 和 stop 来赋值:

def my_consecutive_bools_loop(ar):
    indices, = np.concatenate([ar[:1], ar[:-1] != ar[1:], ar[-1:]]).nonzero()
    result = np.zeros((indices.size // 2, ar.size), bool)
    for row, start, stop in zip(result, indices[::2], indices[1::2]):
        row[start:stop] = True
    return result

简单基准:

In [_]: rng = np.random.default_rng()

In [_]: small = rng.choice([True, False], 100, p=[0.8, 0.2])

In [_]: big = rng.choice([True, False], 100000, p=[0.8, 0.2])

In [_]: %timeit consecutive_bools(small)
109 µs ± 286 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

In [_]: %timeit my_consecutive_bools(small)
13.3 µs ± 46.7 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)

In [_]: %timeit my_consecutive_bools_loop(small)
20 µs ± 122 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)

In [_]: %timeit consecutive_bools(big)
699 ms ± 6.62 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [_]: %timeit my_consecutive_bools(big)
2.98 s ± 17 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [_]: %timeit my_consecutive_bools_loop(big)
33.4 ms ± 1.15 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

这可以通过创建一个数组来实现,每个 True 条纹都分配有一个不同的组标识符。 使用反转的 True/False 的累积和可以产生对于连续的 True 位置保持相同的组号。 将其与原始数组相交会将 False position 放入第 0 组(我们可以排除)。 通过将唯一组编号与组序列(在广播中)进行比较,可以获得 True 条纹的预期矩阵:

import numpy as np
    
x=np.array([True, True, False, True, False])

groups = (~x).cumsum() + 1
trues  = np.unique(groups[x])[:,None] == groups * x

print(trues)

[[ True  True False False False]
 [False False False  True False]]

适用于任意数量的组(您不需要提前知道):

x=np.array([False,True,True,True,False,True,False,False,True,True,True])

groups = (~x).cumsum() + 1
trues  = np.unique(groups[x])[:,None] == groups * x

print(trues)

[[False  True  True  True False False False False False False False]
 [False False False False False  True False False False False False]
 [False False False False False False False False  True  True  True]]

这应该比任何涉及循环的解决方案都快得多。 它也更加简洁。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM