Python/Numpy/Boolean 索引:在具有 N 个连续真值的位置处修改布尔数组

Python/Numpy/Boolean Indexing: Modify boolean array at locations with N consecutive True values

给定一个 numpy 布尔数组

arr = np.array([1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1])

我想指出哪里至少有n个连续的真值(从左到右)。

对于n = 2:

#         True 2x (or more) in a row
#            /  \        /     \
arr = [1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1]

#                 becomes:

res = [0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0]
#               ^-----------^--^-------- A pattern of 2 or more consecutive True's ends at each of these locations

对于n = 3:

#          True 3x (or more) in a row
#                        /     \
arr = [1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1]

#                 becomes:

res = [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0]
#                              ^-------- A pattern of 3 or more consecutive True's ends at this location

是否有一个 pythonic 方法来解决这个 而没有 使用 for 循环遍历每个元素?性能在这里很重要,因为我的应用程序将对包含 1000 个元素的布尔数组执行此操作 1000 次。

值得一提的注意事项:

  1. n 可以是大于 2
  2. 的任何值
  3. n——连续的模式可以出现在数组的任何地方;开头、中间或结尾。
  4. 结果数组的形状必须与原始数组的形状匹配。

非常感谢任何帮助。

答案基准

fully vectorized by alain-t:
10000 loops, best of 5: 0.138 seconds per loop, worse of 5: 0.149 seconds per loop

pad/shift by mozway:
10000 loops, best of 5: 1.62 seconds per loop, worse of 5: 1.71 seconds per loop

sliding_window_view by kubatucka (with padding by mozway):
10000 loops, best of 5: 1.15 seconds per loop, worse of 5: 1.54 seconds per loop

您可以将每个元素与其前身相乘。这将为您提供两个或更多序列的 1s。对结果再做一次以获得 3 个或更多的序列:

import numpy as np
arr = np.array([1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1])

arr[1:] *= arr[:-1]   # two consecutive
arr[0]  = 0           # 1st '1' isn't a two-consec.
arr[1:] *= arr[:-1]   # three consecutive 

print(arr)
[0 0 0 0 0 0 0 0 1 0 0]

你也可以这样试试(速度会快一点):

import numpy as np
arr = np.array([1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1])

arr[2:] *= arr[:-2] * arr[1:-1]
arr[:2] = 0

print(arr)
[0 0 0 0 0 0 0 0 1 0 0]

n 个连续的通用解决方案无法使用第二种方法,但可以使用第一种方法在循环中执行:

n = 3
arr[1:]  *= arr[:-1]
arr[:n-1] = 0
for i in range(n-2):
    arr[1:] *= arr[:-1]

请注意,这失去了矢量化的一些好处。

对于完全矢量化的 n 连续方法,您可以 select 最大 运行 个 0 位置与所有项目位置之间的匹配差异。结果的 1 位将包含该位置连续 1 的个数(而 0 位将为零)

n = 3
i = np.arange(arr.size)+1
mask = n <= i-np.maximum.accumulate((1-arr)*i) #True/False array

print(mask*1)
[0 0 0 0 0 0 0 0 1 0 0]

视觉上:

arr                  [ 1  0  1  1  0  0  1  1  1  0   1  ]
i                    [ 1  2  3  4  5  6  7  8  9  10  11 ] -+
(1-arr)*i            [ 0  2  0  0  5  6  0  0  0  10  0  ]  |-> differences
maximum.accumulate   [ 0  2  2  2  5  6  6  6  6  10  10 ] -+     |
i-np.maximum...      [ 1  0  1  2  0  0  1  2  3  0   1  ] <------+

作为这种方法的一个附带好处,您实际上可以从 i-np.maximum.accumulate((1-arr)*i) 的一次操作中获得所有 n 个连续的值,因此您可以存储它们并检查 n 的不同值,而无需重做计算。

仅限 Numpy:

from numpy.lib.stride_tricks import sliding_window_view

arr = np.array([1, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1])
res = sliding_window_view(arr , window_shape = 3).all(axis=1) # window_shape <- n
>>> array([False, False, False, False, False, False,  True, False, False])

你可以pad/shift序列使用numpy.pad并与之前的值进行比较,最后乘以原始值只保留1:

b = np.pad(arr, (1,0), constant_values=0)[:-1]
# b: array([0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 0])
(arr==b)*arr

输出:array([0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0])

通用解决方案:

n = 3
(sum([np.pad(arr, (i, 0), constant_values=0)[:len(arr)]
      for i in range(n)]) == n)*arr

输出:array([0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0])

注意。这里的技巧是sum移位的值,如果前n个值为1,则总和为n