Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Rolling window #31

Open
wants to merge 1 commit into from

8 participants

@rigtorp

A useful stride trick that not many people are aware of.

@dwf

Looks good to me. One question: does this assume C-contiguity?

@rgommers
Owner

Can this work for other axes too, or only the last one? That feels a bit too specific.

It would also needs a few tests before it can be added.

The last axis seems like the natural place for it, though I'm having a hard time finding the words as to why.

+1 on tests though.

Owner

I don't really get why the last axis would be more useful, it depends on the data in your array. If I want a rolling window view of the first axis, it would be annoying to first have to roll axes around because this function only works with the last axis.

It can work with any axis if I add a swapaxis call in there. Problem is where should the new axis go?

Owner

New axis always in the same place I'd think, it becomes either the first or last axis.

I suggest a new keyword "axis", with the standard default value (always forget if that's -1 or None, easy to check with other function signatures).

In the docstring it would be good where the extra axis goes and what the shape is of the returned array. It took me a little while to figure out why the new shape was (2, 3, 3) for an input of shape (2, 5) and window=1.

Finally, I think it would be easier if window is a keyword with a default value of 1.

@WarrenWeckesser

It seems natural to me for the axis to change "in place"; that is, the window axis is immediately after the axis being "windowed". I'd also like a "step" keyword that determines the increment of the start of each window. This should do it:

def rolling_window(a, window=1, step=1, axis=-1):
    if window < 1:  
        raise ValueError("`window` must be at least 1.")
    if window > a.shape[axis]:
        raise ValueError("`window` is too long.")
    if step < 1:
        raise ValueError("`step` must be at least 1.")
    axis = axis % a.ndim
    num_windows = (a.shape[axis] - window + step) / step
    shape = a.shape[:axis] + (num_windows, window) + a.shape[axis + 1:]
    strides = a.strides[:axis] + (step * a.strides[axis], a.strides[axis]) + a.strides[axis + 1:]
    return as_strided(a, shape=shape, strides=strides)

For example,

In [74]: a
Out[74]: 
array([[ 0,  1,  2,  3,  4,  5],
       [ 6,  7,  8,  9, 10, 11],
       [12, 13, 14, 15, 16, 17],
       [18, 19, 20, 21, 22, 23]])

In [75]: rolling_window(a, window=3, step=2)
Out[75]: 
array([[[ 0,  1,  2],
        [ 2,  3,  4]],

       [[ 6,  7,  8],
        [ 8,  9, 10]],

       [[12, 13, 14],
        [14, 15, 16]],

       [[18, 19, 20],
        [20, 21, 22]]])

In [76]: rolling_window(a, window=2, axis=0)
Out[76]: 
array([[[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11]],

       [[ 6,  7,  8,  9, 10, 11],
        [12, 13, 14, 15, 16, 17]],

       [[12, 13, 14, 15, 16, 17],
        [18, 19, 20, 21, 22, 23]]])

In [77]: _.shape
Out[77]: (3, 2, 6)

In [78]: rolling_window(a, window=2, step=2, axis=0)
Out[78]: 
array([[[ 0,  1,  2,  3,  4,  5],
        [ 6,  7,  8,  9, 10, 11]],

       [[12, 13, 14, 15, 16, 17],
        [18, 19, 20, 21, 22, 23]]])

In [79]: _.shape
Out[79]: (2, 2, 6)
@pv
Owner
pv commented

Additional feature suggestion: How about 2-D (and higher dimensional) windows? These may also useful, and it would be nice to have everything done by a single function.

@seberg
Owner

Just to note, something like this is very easy to generalize for n-dimensions, of course that would make an axis argument a bit ill defined as to what it would do, I guess it would be possible to add it when window is scalar, and maybe a choice as to add the new dimensions at the start or end. If window == step, it creates non overlapping "tiles".

EDIT: I created a new very general version which allows for pretty much all of the above (I could imagine adding axis argument to build a up the correct window), also there is no support for negative steps, but I am not sure that matters. I guess the comments are not understandable probably...

sorry for the spam, have put that code here: https://gist.github.com/3866040 instead.

@insertinterestingnamehere

What's the status of this? It looks like @seberg already has a working implementation. What's left?

@njsmith
Owner
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jan 1, 2011
  1. @rigtorp
This page is out of date. Refresh to see the latest.
Showing with 38 additions and 1 deletion.
  1. +38 −1 numpy/lib/stride_tricks.py
View
39 numpy/lib/stride_tricks.py
@@ -7,7 +7,7 @@
"""
import numpy as np
-__all__ = ['broadcast_arrays']
+__all__ = ['broadcast_arrays', 'rolling_window']
class DummyArray(object):
""" Dummy object that just exists to hang __array_interface__ dictionaries
@@ -113,3 +113,40 @@ def broadcast_arrays(*args):
broadcasted = [as_strided(x, shape=sh, strides=st) for (x,sh,st) in
zip(args, shapes, strides)]
return broadcasted
+
+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))
+ >>> np.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(np.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 as_strided(a, shape=shape, strides=strides)
Something went wrong with that request. Please try again.