我有一个numpy数组(6,2)
[[00,01],
[10,11],
[20,21],
[30,31],
[40,41],
[50,51]]
我需要一个步长为1且窗口大小为3的滑动窗口:
[[00,01,10,11,20,21],
[10,11,20,21,30,31],
[20,21,30,31,40,41],
[30,31,40,41,50,51]]
我正在寻找一个笨拙的解决方案。如果您的解决方案可以参数化原始数组的形状以及窗口大小和步长,那就太好了。
我找到了相关的答案Using strides for an efficient moving average filter,但我没有看到如何指定那里的步长以及如何将窗口从3d折叠到连续的2d数组。另外这个Rolling or sliding window iterator in Python但是在Python中,我不确定它的效率如何。此外,它支持元素,但如果每个元素都有多个特征,则最终不会将它们连接在一起。
答案 0 :(得分:30)
您可以使用花式索引在numpy中执行矢量化滑动窗口。
>>> import numpy as np
>>> a = np.array([[00,01], [10,11], [20,21], [30,31], [40,41], [50,51]])
>>> a
array([[ 0, 1],
[10, 11],
[20, 21], #define our 2d numpy array
[30, 31],
[40, 41],
[50, 51]])
>>> a = a.flatten()
>>> a
array([ 0, 1, 10, 11, 20, 21, 30, 31, 40, 41, 50, 51]) #flattened numpy array
>>> indexer = np.arange(6)[None, :] + 2*np.arange(4)[:, None]
>>> indexer
array([[ 0, 1, 2, 3, 4, 5],
[ 2, 3, 4, 5, 6, 7], #sliding window indices
[ 4, 5, 6, 7, 8, 9],
[ 6, 7, 8, 9, 10, 11]])
>>> a[indexer]
array([[ 0, 1, 10, 11, 20, 21],
[10, 11, 20, 21, 30, 31], #values of a over sliding window
[20, 21, 30, 31, 40, 41],
[30, 31, 40, 41, 50, 51]])
>>> np.sum(a[indexer], axis=1)
array([ 63, 123, 183, 243]) #sum of values in 'a' under the sliding window.
解释此代码的用途。
np.arange(6)[None, :]
创建一个行向量0到6,np.arange(4)[:, None]
创建一个列向量0到4.这导致一个4x6矩阵,其中每一行(其中六个)代表一个窗口,并且行数(其中四个)表示窗口数。 2的倍数使滑动窗口每次滑动2个单位,这是在每个元组上滑动所必需的。使用numpy数组切片,你可以将滑动窗口传递给展平的numpy数组,并像总和那样聚合它们。
答案 1 :(得分:25)
In [1]: import numpy as np
In [2]: a = np.array([[00,01], [10,11], [20,21], [30,31], [40,41], [50,51]])
In [3]: w = np.hstack((a[:-2],a[1:-1],a[2:]))
In [4]: w
Out[4]:
array([[ 0, 1, 10, 11, 20, 21],
[10, 11, 20, 21, 30, 31],
[20, 21, 30, 31, 40, 41],
[30, 31, 40, 41, 50, 51]])
您可以将其写为函数:
def window_stack(a, stepsize=1, width=3):
n = a.shape[0]
return np.hstack( a[i:1+n+i-width:stepsize] for i in range(0,width) )
只要a.ndim = 2
,这并不真正取决于原始数组的形状。请注意,我从不在交互式版本中使用任何长度。形状的第二个维度是无关紧要的;每行可以随意长。感谢@ Jaime的建议,你可以在不检查形状的情况下完成:
def window_stack(a, stepsize=1, width=3):
return np.hstack( a[i:1+i-width or None:stepsize] for i in range(0,width) )
答案 2 :(得分:21)
解决方案是
np.lib.stride_tricks.as_strided(a, shape=(4,6), strides=(8,4))
。
当你开始考虑指针/地址时,使用步幅很直观。
as_strided()
方法有3个参数。
data 是我们要操作的数组。
要使用as_strided()
来实现滑动窗口函数,我们必须事先计算输出的形状。在问题中,(4,6)是输出的形状。如果尺寸不正确,我们最终会读取垃圾值。这是因为我们通过将指针移动几个字节来访问数据(取决于数据类型)。
确定步幅的正确值对于获得预期结果至关重要。
在计算步幅之前,使用arr.strides[-1]
找出每个元素占用的内存。在此示例中,一个元素占用的内存为4个字节。
Numpy数组以行主要方式创建。下一行的第一个元素紧挨着当前行的最后一个元素。
例: 0,1 | 10,11 | ...
10就在1旁边。
想象一下,2D阵列重新形成1D(这是可以接受的,因为数据以行主格式存储)。输出中每行的第一个元素是1D数组中的奇数索引元素。 0,10,20,30,..
因此,我们需要从0到10,10到20等内存中的步骤数是 2 *元素的内存大小。每行的步幅为2 * 4bytes = 8。 对于输出中的给定行,所有元素在我们想象的1D数组中彼此相邻。要获得连续的下一个元素,只需要一个等于元素大小的步幅。列stride的值是4个字节。
因此,strides=(8,4)
另一种解释:
输出的形状为(4,6)。列步长4
。因此,第一行元素从索引0
开始,并且有6个元素,每个元素间隔4个字节。
收集第一行后,第二行从当前行的开始开始8个字节。第三行从第二行的起点开始8个字节,依此类推。
Shape确定我们需要的行数和列数。 strides定义内存步骤以开始行并收集列元素
答案 3 :(得分:7)
使用more_itertools.windowed
1 可以实现短列表理解:
<强>鉴于强>
import numpy as np
import more_itertools as mit
a = [["00","01"],
["10","11"],
["20","21"],
["30","31"],
["40","41"],
["50","51"]]
b = np.array(a)
<强>代码强>
np.array([list(mit.flatten(w)) for w in mit.windowed(a, n=3)])
或
np.array([[i for item in w for i in item] for w in mit.windowed(a, n=3)])
或
np.array(list(mit.windowed(b.ravel(), n=6)))
输出
array([['00', '01', '10', '11', '20', '21'],
['10', '11', '20', '21', '30', '31'],
['20', '21', '30', '31', '40', '41'],
['30', '31', '40', '41', '50', '51']],
dtype='<U2')
创建并展平尺寸为n=3
的滑动窗口。请注意,默认步长为more_itertools.windowed(..., step=1)
。
<强>性能强>
作为一个数组,接受的答案是最快的。
%timeit np.hstack((a[:-2], a[1:-1], a[2:]))
# 37.5 µs ± 1.88 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit np.hstack((b[:-2], b[1:-1], b[2:]))
# 12.9 µs ± 166 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit np.array([list(mit.flatten(w)) for w in mit.windowed(a, n=3)])
# 23.2 µs ± 1.73 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit np.array([[i for item in w for i in item] for w in mit.windowed(a, n=3)])
# 21.2 µs ± 999 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit np.array(list(mit.windowed(b.ravel(), n=6)))
# 43.4 µs ± 374 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
实施itertool recipes的第三方库和许多有用的工具。
答案 4 :(得分:7)
从Numpy 1.20
开始,使用新的sliding_window_view
来滑动/滚动元素窗口,基于与user42541's answer相同的想法,我们可以这样做:
import numpy as np
from numpy.lib.stride_tricks import sliding_window_view
# values = np.array([[0,1], [10,11], [20,21], [30,31], [40,41], [50,51]])
sliding_window_view(values.flatten(), window_shape = 2*3)[::2]
# array([[ 0, 1, 10, 11, 20, 21],
# [10, 11, 20, 21, 30, 31],
# [20, 21, 30, 31, 40, 41],
# [30, 31, 40, 41, 50, 51]])
其中 2
是子数组的大小,3
是窗口。
中间步骤的详细信息:
# values = np.array([[0,1], [10,11], [20,21], [30,31], [40,41], [50,51]])
# Flatten the array (concatenate sub-arrays):
values.flatten()
# array([ 0, 1, 10, 11, 20, 21, 30, 31, 40, 41, 50, 51])
# Slide through windows of size 2*3=6:
sliding_window_view(values.flatten(), 2*3)
# array([[ 0, 1, 10, 11, 20, 21],
# [ 1, 10, 11, 20, 21, 30],
# [10, 11, 20, 21, 30, 31],
# [11, 20, 21, 30, 31, 40],
# [20, 21, 30, 31, 40, 41],
# [21, 30, 31, 40, 41, 50],
# [30, 31, 40, 41, 50, 51]])
# Only keep even rows (1 row in 2 - if sub-arrays have a size of x, then replace 2 with x):
sliding_window_view(values.flatten(), 2*3)[::2]
# array([[ 0, 1, 10, 11, 20, 21],
# [10, 11, 20, 21, 30, 31],
# [20, 21, 30, 31, 40, 41],
# [30, 31, 40, 41, 50, 51]])
答案 5 :(得分:3)
从 NumPy 版本 1.20.0
开始,这可以使用
np.lib.stride_tricks.sliding_window_view(arr, winsize)
示例:
>>> arr = np.arange(0, 9).reshape((3, 3))
>>> np.lib.stride_tricks.sliding_window_view(arr, (2, 2))
array([[[[0, 1],
[3, 4]],
[[1, 2],
[4, 5]]],
[[[3, 4],
[6, 7]],
[[4, 5],
[7, 8]]]])
您可以阅读更多相关信息here。
答案 6 :(得分:0)
这是一个纯Python实现:
def sliding_window(arr, window=3):
i = iter(arr)
a = []
for e in range(0, window): a.append(next(i))
yield a
for e in i:
a = a[1:] + [e]
yield a
一个例子:
# flatten array
flatten = lambda l: [item for sublist in l for item in sublist]
a = [[0,1], [10,11], [20,21], [30,31], [40,41], [50,51]]
w = sliding_window(a, width=3)
print( list(map(flatten,w)) )
[[0, 1, 10, 11, 20, 21], [10, 11, 20, 21, 30, 31], [20, 21, 30, 31, 40, 41], [30, 31, 40, 41, 50, 51]]
基准
import timeit
def benchmark():
a = [[0,1], [10,11], [20,21], [30,31], [40,41], [50,51]]
sliding_window(a, width=3)
times = timeit.Timer(benchmark).repeat(3, number=1000)
time_taken = min(times) / 1000
print(time_taken)
1.0944640007437556e-06
答案 7 :(得分:0)
splits = np.vstack(np.split(x,np.array([[i, i+3] for i in range(x.shape[0] - x.shape[1])]).reshape(-1))).reshape(-1, 6)
x = np.array([[00,1],
[10,11],
[20,21],
[30,31],
[40,41],
[50,51]])
[[ 0 1 10 11 20 21]
[10 11 20 21 30 31]
[20 21 30 31 40 41]
[30 31 40 41 50 51]]
import numpy as np
import time
x = np.array(range(1000)).reshape(-1, 2)
all_t = 0.
for i in range(1000):
start_ = time.time()
np.vstack(
numpy.split(x,np.array([[i, i+3] for i in range(x.shape[0] - x.shape[1])])
.reshape(-1))).reshape(-1, 6)
all_t += time.time() - start_
print('Average Time of 1000 Iterations on Array of Shape '
'1000 x 2 is: {} Seconds.'.format(all_t/1000.))
Average Time of 1000 Iterations on Array of Shape 1000 x 2 is: 0.0016909 Seconds.