An interesting method is to construct the start and stop of each segment, and then construct an array through np.arange(x.size)
. Compare it and all starts with >=
, and compare it and all stops with <
. The logical and of the two results yields the desired 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]])
This method works well on some small arrays, but its time complexity is high. For large arrays, you can simply iterate over start and stop to assign values:
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
Simple benchmark:
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)