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