2

给定一个 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

在不使用 for 循环遍历每个元素的情况下,是否有一种 Pythonic 方法?性能在这里很重要,因为我的应用程序将在包含 1000 个元素的布尔数组上执行此操作 1000 次。

值得一提的注意事项:

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

任何帮助将不胜感激。

答案的基准

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
4

3 回答 3

2

您可以将每个元素与其前任相乘。这将为您提供两个或更多序列的 1。在结果上再次执行以获得 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-consecutives 的广义解决方案不能使用第二种方法,但可以使用第一种方法在循环中执行:

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

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

对于完全向量化的 n 连续方法,您可以选择 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  ] <------+

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

于 2021-11-08T16:02:16.827 回答
1

仅 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])

于 2021-11-08T16:04:19.480 回答
1

您可以使用前一个值填充/移位序列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

于 2021-11-08T16:33:26.867 回答