Существует умный (читай "пограничный умник") способ сделать это с помощью np.lib.stride_tricks.as_strided
.as_strided
позволяет вам создавать представления в вашем буфере, которые имитируют окна, добавляя другое измерение в представление.Например, если у вас был одномерный массив, например
>>> x = np.arange(10)
>>> np.lib.stride_tricks.as_strided(x, shape=(3, x.shape[0] - 2), strides=x.strides * 2)
array([[0, 1, 2, 3, 4, 5, 6, 7],
[1, 2, 3, 4, 5, 6, 7, 8],
[2, 3, 4, 5, 6, 7, 8, 9]])
Надеюсь, понятно, что вы можете просто суммировать по axis=0
, чтобы получить сумму для каждого окна размера 3.Нет причины, по которой вы не смогли бы вытянуть это в два или более измерения.Я написал форму и индекс предыдущего примера таким образом, который предлагает решение:
A = np.array([[0, 0, 0, 0, 0],
[0, 1, 0, 1, 0],
[0, 1, 1, 0, 0],
[0, 1, 0, 1, 0],
[0, 0, 0, 0, 0]])
view = np.lib.stride_tricks.as_strided(A,
shape=(3, 3, A.shape[0] - 2, A.shape[1] - 2),
strides=A.strides * 2
)
B[1:-1, 1:-1] = view.sum(axis=(0, 1))
Суммирование по нескольким осям одновременно поддерживается в np.sum начиная с v1.7,0.Для более старых версий numpy просто суммируйте многократно (дважды) по axis=0
.
Заполнение краев B
оставлено в качестве упражнения для читателя (так как это на самом деле не является частью вопроса).
Кроме того, решение здесь состоит из одной строки, если вы хотите, чтобы это было.Лично я думаю, что что-либо с as_strided
уже достаточно неразборчиво и не нуждается в дальнейшей запутанности.Я не уверен, что цикл for
будет достаточно плохим с точки зрения производительности, чтобы оправдать этот метод на самом деле.
Для дальнейшего использования приведу обобщенную функцию создания окон, которую можно использовать длярешить проблему такого рода:
def window_view(a, window=3):
"""
Create a (read-only) view into `a` that defines window dimensions.
The first ``a.ndim`` dimensions of the returned view will be sized according to `window`.
The remaining ``a.ndim`` dimensions will be the original dimensions of `a`, truncated by `window - 1`.
The result can be post-precessed by reducing the leading dimensions. For example, a multi-dimensional moving average could look something like ::
window_view(a, window).sum(axis=tuple(range(a.ndim))) / window**a.ndim
If the window size were different for each dimension (`window` were a sequence rather than a scalar), the normalization would be ``np.prod(window)`` instead of ``window**a.ndim``.
Parameters
-----------
a : array-like
The array to window into. Due to numpy dimension constraints, can not have > 16 dims.
window :
Either a scalar indicating the window size for all dimensions, or a sequence of length `a.ndim` providing one size for each dimension.
Return
------
view : numpy.ndarray
A read-only view into `a` whose leading dimensions represent the requested windows into `a`.
``view.ndim == 2 * a.ndim``.
"""
a = np.array(a, copy=False, subok=True)
window = np.array(window, copy=False, subok=False, dtype=np.int)
if window.size == 1:
window = np.full(a.ndim, window)
elif window.size == a.ndim:
window = window.ravel()
else:
raise ValueError('Number of window sizes must match number of array dimensions')
shape = np.concatenate((window, a.shape))
shape[a.ndim:] -= window - 1
strides = a.strides * 2
return np.lib.stride_tricks.as_strided(a, shake=shape, strides=strides)