Hey, Inspired by an existing PR into numpy, I created two functions based on stride_tricks which I thought might be useful inside numpy. So if I get any feedback/pointers, I would add some tests and create a PR.
The first function rolling_window is to create for every point of the original array, a view of its neighbourhood in arbitrary dimensions with some options to make it easy to use. For example for a 3 dim array: rolling_window(a, (3,3,3), asteps=(3,3,3)).max((3,4,5)) Gives the maximum for all non-overlapping 3x3x3 subarrays and: rolling_window(a, 3, axes=0).max(-1) would create the rolling maximum over all 3 successive values along the 0th axis. Plus a function permute_axes which allows to give a tuple for switching axes and can also merge them. A (10, 4, 3) shaped array would give: permute_axes(a, (2, 1, 0)).shape -> (3, 4, 10) permute_axes(a, (1, (0, 2))).shape -> (4, 30) A copy is created when necessary, this basically allows to do multiple swap_axes and a reshape in a single call. I hope this might be useful... Regards, Sebastian
import numpy as np def rolling_window(array, window=(0,), asteps=None, wsteps=None, axes=None, intersperse=False): """Create a view of `array` which for every point gives the n-dimensional neighbourhood of size window. New dimensions are added at the end of `array` or after the corresponding original dimension. Parameters ---------- array : array_like Array to which the rolling window is applied. window : int or tuple Either a single integer to create a window of only the last axis or a tuple to create it for the last len(window) axes. 0 can be used as a to ignore a dimension in the window. asteps : tuple Aligned at the last axis, new steps for the original array, ie. for creation of non-overlapping windows. (Equivalent to slicing result) wsteps : int or tuple (same size as window) steps for the added window dimensions. These can be 0 to repeat values along the axis. axes: int or tuple If given, must have the same size as window. In this case window is interpreted as the size in the dimension given by axes. IE. a window of (2, 1) is equivalent to window=2 and axis=-2. intersperse : bool If True, the new dimensions are right after the corresponding original dimension, instead of at the end of the array. Returns ------- A view on `array` which is smaller to fit the windows and has windows added dimensions (0s not counting), ie. every point of `array` is an array of size window. Examples -------- >>> a = np.arange(9).reshape(3,3) >>> rolling_window(a, (2,2)) array([[[[0, 1], [3, 4]], [[1, 2], [4, 5]]], [[[3, 4], [6, 7]], [[4, 5], [7, 8]]]]) Or to create non-overlapping windows, but only along the first dimension: >>> rolling_window(a, (2,0), asteps=(2,1)) array([[[0, 3], [1, 4], [2, 5]]]) Note that the 0 is discared, so that the output dimension is 3: >>> rolling_window(a, (2,0), asteps=(2,1)).shape (1, 3, 2) This is useful for example to calculate the maximum in all (overlapping) 2x2 submatrixes: >>> rolling_window(a, (2,2)).max((2,3)) array([[4, 5], [7, 8]]) Or delay embedding (3D embedding with delay 2): >>> x = np.arange(10) >>> rolling_window(x, 3, wsteps=2) array([[0, 2, 4], [1, 3, 5], [2, 4, 6], [3, 5, 7], [4, 6, 8], [5, 7, 9]]) """ array = np.asarray(array) orig_shape = np.asarray(array.shape) window = np.atleast_1d(window).astype(int) # maybe crude to cast to int... if axes is not None: axes = np.atleast_1d(axes) w = np.zeros(array.ndim, dtype=int) for axis, size in zip(axes, window): w[axis] = size window = w # Check if window is legal: if window.ndim > 1: raise ValueError("`window` must be one-dimensional.") if np.any(window < 0): raise ValueError("All elements of `window` must be larger then 1.") if len(array.shape) < len(window): raise ValueError("`window` length must be less or equal `array` dimension.") _asteps = np.ones_like(orig_shape) if asteps is not None: asteps = np.atleast_1d(asteps) if asteps.ndim != 1: raise ValueError("`asteps` must be either a scalar or one dimensional.") if len(asteps) > array.ndim: raise ValueError("`asteps` cannot be longer then the `array` dimension.") # does not enforce alignment, so that steps can be same as window too. _asteps[-len(asteps):] = asteps if np.any(asteps < 1): raise ValueError("All elements of `asteps` must be larger then 1.") asteps = _asteps _wsteps = np.ones_like(window) if wsteps is not None: wsteps = np.atleast_1d(wsteps) if wsteps.shape != window.shape: raise ValueError("`wsteps` must have the same shape as `window`.") if np.any(wsteps < 0): raise ValueError("All elements of `wsteps` must be larger then 0.") _wsteps[:] = wsteps _wsteps[window == 0] = 1 # make sure that steps are 1 for non-existing dims. wsteps = _wsteps # Check that the window would not be larger then the original: if np.any(orig_shape[-len(window):] < window * wsteps): raise ValueError("`window` * `wsteps` larger then `array` in at least one dimension.") new_shape = orig_shape # just renaming... # For calculating the new shape 0s must act like 1s: _window = window.copy() _window[_window==0] = 1 new_shape[-len(window):] += wsteps - _window * wsteps new_shape = (new_shape + asteps - 1) // asteps # make sure the new_shape is at least 1 in any "old" dimension (ie. steps # is (too) large, but we do not care. new_shape[new_shape < 1] = 1 shape = new_shape strides = np.asarray(array.strides) strides *= asteps new_strides = array.strides[-len(window):] * wsteps # The full new shape and strides: if not intersperse: new_shape = np.concatenate((shape, window)) new_strides = np.concatenate((strides, new_strides)) else: _ = np.zeros_like(shape) _[-len(window):] = window _window = _.copy() _[-len(window):] = new_strides _new_strides = _ new_shape = np.zeros(len(shape)*2, dtype=int) new_strides = np.zeros(len(shape)*2, dtype=int) new_shape[::2] = shape new_strides[::2] = strides new_shape[1::2] = _window new_strides[1::2] = _new_strides new_strides = new_strides[new_shape != 0] new_shape = new_shape[new_shape != 0] return np.lib.stride_tricks.as_strided(array, shape=new_shape, strides=new_strides) def permute_axes(array, axes, copy=False, order='C'): """Change the arrays axes order or combine multiple axes into one. Creates a view if possible, but when axes are combined will usually return a copy. Parameters ---------- array : array_like Array for which to define new axes. axes : iterable Iterable giving for every new axis which old axis are combined into it. np.newaxis/None can be used to insert a new axis. Elements must be either ints or iterables of ints identifying each axis. copy : bool If True a copy is forced. order : 'C', 'F', 'A' or 'K' Whether new array should have C or Fortran order. See np.copy help for details. See Also -------- swapaxes, rollaxis, reshape Examples -------- >>> a = np.arange(12).reshape(3,2,2) Just reverse the axes order: >>> permute_axes(a, (2, 1, 0)) array([[[ 0, 4, 8], [ 2, 6, 10]], [[ 1, 5, 9], [ 3, 7, 11]]]) Combine axis 0 and 1 and put the last axis to the front: >>> permute_axes(a, (-1, (0, 1))) array([[ 0, 2, 4, 6, 8, 10], [ 1, 3, 5, 7, 9, 11]]) Or going over the first two axes in different order: >>> permute_axes(a, (-1, (1, 0))) array([[ 0, 4, 8, 2, 6, 10], [ 1, 5, 9, 3, 7, 11]]) """ new_axes = [] for a in axes: if a is None: new_axes.append(None) else: a = np.atleast_1d(a) if a.ndim > 1: raise ValueError("All items of `axes` must be zero or one dimensional.") new_axes.append(a) # array for slicing. old_shape = np.asarray(array.shape) old_strides = np.asarray(array.strides) # Shape and strides for the copy operation: tmp_shape = [] tmp_strides = [] final_shape = [] final_strides = [] # only used if no copy is needed. check_axes = np.zeros(len(old_shape), dtype=bool) must_copy = False # create a reordered array first: for ax, na in enumerate(new_axes): if na is not None: if np.any(check_axes[na]) or np.unique(na).shape != na.shape: raise ValueError("All axis must at most occure once in the new array") check_axes[na] = True if len(na) != 0: _shapes = old_shape[na] _strides = old_strides[na] tmp_shape.extend(_shapes) tmp_strides.extend(_strides) final_strides.append(_strides.min()) # smallest stride... final_shape.append(_shapes.prod()) if not must_copy: # If any of the strides do not fit together we must copy: prev_stride = _strides[0] for stride, shape in zip(_strides[1:], _shapes[1:]): if shape == 1: # 1 sized dimensions just do not matter, but they # also do not matter for legality check. continue elif prev_stride != stride * shape: must_copy = True break prev_stride = stride continue # skip adding empty axis. tmp_shape.append(1) tmp_strides.append(0) final_shape.append(1) final_strides.append(0) if not must_copy: result = np.lib.stride_tricks.as_strided(array, shape=final_shape, strides=final_strides) if copy: return result.copy(order=order) return result # No need for explicite copy, as reshape must already create one since # must_copy is True. copy_from = np.lib.stride_tricks.as_strided(array, shape=tmp_shape, strides=tmp_strides) return copy_from.reshape(final_shape, order=order)
_______________________________________________ NumPy-Discussion mailing list NumPy-Discussion@scipy.org http://mail.scipy.org/mailman/listinfo/numpy-discussion