На самом деле, есть еще более эффективный способ сделать это ... Недостатком использования vstack
и т. Д. Является то, что вы делаете копию массива.
Кстати, это фактически совпадает с ответом @ Paul, но я публикую это только для того, чтобы объяснить что-то более подробно ...
Есть способ сделать это с помощью просто просмотровтак что нет памяти дублируется.
Я напрямую заимствую это из пост Эрика Ригторпа в numpy-обсуждение , который, в свою очередь, позаимствовал его у Кита Гудмана1012 * Узкое место (что весьма полезно!).
Основная хитрость заключается в том, чтобы напрямую манипулировать шагами массива (для одномерных массивов):
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-мерного массива, мы получаем функцию «скользящего окна» Эрика Ригторпа:
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-разрядными числами с плавающей запятой длина каждого элемента составляет 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
Очевидно, что шаги массива и форма массива тесно связаны между собой.Если мы изменим один, мы должны изменить другой соответственно, иначе у нас не будет правильного описания буфера памяти, который фактически содержит значения массива.
Поэтому, если вы хотите изменить одновременно форма и размер массива одновременно, вы не можете сделать это, просто установив x.strides
и x.shape
, даже если новые шаги и форма совместимы.
Вот тут и появляется numpy.lib.as_strided
. На самом деле это очень простая функция, которая одновременно устанавливает шаги и форму массива одновременно.
Он проверяет, совместимы ли эти два, но не совместимы ли старые шаги и новая форма, как это было бы, если бы вы установили два независимо.(На самом деле это делается с помощью numpy's __array_interface__
, что позволяет произвольным классам описывать буфер памяти как массив numpy.)
Итак, все, что мы сделали, это сделали так, чтобышаг вперед на один элемент (8 байт в случае 64-битного массива) вдоль одной оси, но также только шаг вперед на 8 байт вдоль другой оси .
Другими словами, в случае размера "окна" 3, массив имеет форму (whatever, 3)
, но вместо шага полного 3 * x.itemsize
для второго измерения, он только шаг вперед на один пункт , что делает строки нового массива представлением «движущегося окна» в исходном массиве.
(Это также означает, что x.shape[0] * x.shape[1]
не будет совпадать с x.size
для вашего нового массива.)
В любом случае, надеюсь, это немного прояснит ситуацию.