实际上,有一种更有效的方法来做到这一点...使用
vstack
等的缺点是你正在复制数组。
顺便说一下,这与@Paul的答案基本相同,但我发表这篇文章只是为了更详细地解释一些东西...
有一种方法可以只使用视图来完成这个过程,以便不会复制任何内存。
我直接从
Erik Rigtorp的帖子numpy-discussion中借鉴了这个方法,他又从Keith Goodman的
Bottleneck(非常有用!)中借鉴了这个方法。
基本技巧是直接操作
数组的步幅(对于一维数组):
import numpy as np
def rolling(a, window):
shape = (a.size - window + 1, window)
strides = (a.itemsize, a.itemsize)
return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)
a = np.arange(10)
print rolling(a, 3)
其中a
是您的输入数组,window
是您想要的窗口长度(在您的情况下为3)。
这将产生以下结果:
[[0 1 2]
[1 2 3]
[2 3 4]
[3 4 5]
[4 5 6]
[5 6 7]
[6 7 8]
[7 8 9]]
然而,原始的a
和返回的数组之间绝不会有任何内存重复。这意味着它比其他选项更快且可扩展性明显更好。
例如(使用a = np.arange(100000)
和window=3
):
%timeit np.vstack([a[i:i-window] for i in xrange(window)]).T
1000 loops, best of 3: 256 us per loop
%timeit rolling(a, window)
100000 loops, best of 3: 12 us per loop
如果我们将这个概念推广到N维数组的最后一个轴上的“滚动窗口”,我们就可以得到Erik Rigtorp的“滚动窗口”函数:
import numpy as np
def rolling_window(a, window):
"""
Make an ndarray with a rolling window of the last dimension
Parameters
----------
a : array_like
Array to add rolling window to
window : int
Size of rolling window
Returns
-------
Array that is a view of the original array with a added dimension
of size w.
Examples
--------
>>> x=np.arange(10).reshape((2,5))
>>> rolling_window(x, 3)
array([[[0, 1, 2], [1, 2, 3], [2, 3, 4]],
[[5, 6, 7], [6, 7, 8], [7, 8, 9]]])
Calculate rolling mean of last dimension:
>>> np.mean(rolling_window(x, 3), -1)
array([[ 1., 2., 3.],
[ 6., 7., 8.]])
"""
if window < 1:
raise ValueError, "`window` must be at least 1."
if window > a.shape[-1]:
raise ValueError, "`window` is too long."
shape = a.shape[:-1] + (a.shape[-1] - window + 1, window)
strides = a.strides + (a.strides[-1],)
return np.lib.stride_tricks.as_strided(a, shape=shape, strides=strides)
那么,让我们来看看这里发生了什么......操纵数组的strides
可能看起来有点神奇,但一旦你理解了其中的原理,就不难了。numpy数组的步幅描述了沿着给定轴增加一个值所必须采取的步骤的字节数大小。因此,在64位浮点数的1维数组的情况下,每个项目的长度为8个字节,x.strides
是(8,)
。
x = np.arange(9)
print x.strides
现在,如果我们将其转换为一个二维的3x3数组,步幅将是
(3 * 8, 8)
,因为我们需要跳过24个字节才能沿第一轴增加一步,并且需要跳过8个字节才能沿第二轴增加一步。
y = x.reshape(3,3)
print y.strides
同样,转置操作等同于仅仅颠倒数组的步长:
print y
y.strides = y.strides[::-1]
print y
显然,一个数组的步幅(strides)和形状(shape)是密切相关的。如果改变其中一个,我们必须相应地改变另一个,否则就无法对实际保存数组值的内存缓冲区进行有效描述。
因此,如果您想同时改变数组的形状和大小,您不能仅通过设置x.strides
和x.shape
来完成,即使新的步幅和形状是兼容的。
这就是numpy.lib.as_strided
所起的作用。它实际上是一个非常简单的函数,只需同时设置数组的步幅和形状即可。
它检查两者是否兼容,但不检查旧的步幅和新形状是否兼容,因为这会发生在将两者分别设置时。(它实际上是通过numpy的__array_interface__
来完成的,该接口允许任意类将内存缓冲区描述为numpy数组.)
因此,我们所做的一切都是使它沿一个轴向前移动一个项目(64位数组的8个字节),但同时仅向前移动8个字节。
换句话说,在窗口大小为3的情况下,数组的形状为(whatever, 3)
,但在第二个维度上不是完全移动3*x.itemsize
,而是仅向前移动一个项目,从而有效地使新数组的行成为原始数组的“移动窗口”视图。
这也意味着对于你的新数组,
x.shape[0] * x.shape[1]
不会与
x.size
相同。
无论如何,希望这能让事情稍微清晰一些...
B = array([1,2,3],[2,3,4],[3,4,5],[4,5,6])
不是有效的numpy
! - eatvstack
了10000次!看看我的答案,我只vstack
了一次。 - eumiroB= array([1, ...
仍然不是有效的numpy
代码。但你接受答案的速度真的很快;-)。谢谢。 - eat