Раздвижное окно формы M-by-N numpy.ndarray

У меня есть numpy массив формы (6,2)

[[00,01],
 [10,11],
 [20,21],
 [30,31],
 [40,41],
 [50,51]]

Мне нужно скользящее окно с размером шага 1 и размер окна 3 нравится:

[[00,01,10,11,20,21],
 [10,11,20,21,30,31],
 [20,21,30,31,40,41],
 [30,31,40,41,50,51]]

Я ищу решение с несколькими числами. Если ваше решение может параметризовать форму исходного массива, а также размер окна и размер шага, это здорово.

Я нашел этот связанный ответ Использование шагов для эффективного фильтра скользящей средней, но я не вижу, как указать там порядок и как свернуть окно из 3d в непрерывный массив 2d. Также этот итератор Rolling или slide window в Python, но это в Python, и я не уверен, насколько это эффективно. Кроме того, он поддерживает элементы, но не объединяет их в конце, если каждый элемент имеет несколько функций.

Ответ 1

In [1]: import numpy as np

In [2]: a = np.array([[00,01], [10,11], [20,21], [30,31], [40,41], [50,51]])

In [3]: w = np.hstack((a[:-2],a[1:-1],a[2:]))

In [4]: w
Out[4]: 
array([[ 0,  1, 10, 11, 20, 21],
       [10, 11, 20, 21, 30, 31],
       [20, 21, 30, 31, 40, 41],
       [30, 31, 40, 41, 50, 51]])

Вы можете записать это как функцию:

def window_stack(a, stepsize=1, width=3):
    n = a.shape[0]
    return np.hstack( a[i:1+n+i-width:stepsize] for i in range(0,width) )

Это не зависит от формы исходного массива, пока a.ndim = 2. Обратите внимание, что я никогда не использую ни одной длины в интерактивной версии. Второе измерение формы не имеет значения; каждая строка может быть до тех пор, пока вы хотите. Благодаря предложению @Jaime вы можете сделать это, не проверив форму вообще:

def window_stack(a, stepsize=1, width=3):
    return np.hstack( a[i:1+i-width or None:stepsize] for i in range(0,width) )

Ответ 2

Вы можете сделать векторное скользящее окно в numpy, используя причудливую индексацию.

>>> import numpy as np

>>> a = np.array([[00,01], [10,11], [20,21], [30,31], [40,41], [50,51]])

>>> a
array([[ 0,  1],
       [10, 11],
       [20, 21],                      #define our 2d numpy array
       [30, 31],
       [40, 41],
       [50, 51]])

>>> a = a.flatten()

>>> a
array([ 0,  1, 10, 11, 20, 21, 30, 31, 40, 41, 50, 51])    #flattened numpy array

>>> indexer = np.arange(6)[None, :] + 2*np.arange(4)[:, None]

>>> indexer
array([[ 0,  1,  2,  3,  4,  5],
       [ 2,  3,  4,  5,  6,  7],            #sliding window indices
       [ 4,  5,  6,  7,  8,  9],
       [ 6,  7,  8,  9, 10, 11]])

>>> a[indexer]
array([[ 0,  1, 10, 11, 20, 21],
       [10, 11, 20, 21, 30, 31],            #values of a over sliding window
       [20, 21, 30, 31, 40, 41],
       [30, 31, 40, 41, 50, 51]])

>>> np.sum(a[indexer], axis=1)
array([ 63, 123, 183, 243])         #sum of values in 'a' under the sliding window.

Объяснение того, что делает этот код.

np.arange(6)[None, :] создает вектор строк от 0 до 6, а np.arange(4)[:, None] создает вектор-столбец от 0 до 4. Это приводит к матрице 4x6, где каждая строка (шесть из них) представляет окно, а число строк (четыре из них) представляет количество окон. Множество из 2 делает скользящее окно скольжением 2 единицы за раз, что необходимо для скольжения по каждому кортежу. Используя нарезку массива numpy, вы можете передать скользящее окно в сплющенный массив numpy и сделать агрегаты на них, как сумма.

Ответ 3

Решение

np.lib.stride_tricks.as_strided(a, shape=(4,6), strides=(8,4)).

Использование шагов является интуитивно понятным, когда вы начинаете думать с точки зрения указателей/адресов.

Метод as_strided() имеет 3 аргумента.

  1. данные
  2. форма
  3. успехи

Данные это массив, на котором мы будем работать.

Чтобы использовать as_strided() для реализации скользящих оконных функций, мы должны предварительно вычислить форму вывода. В вопросе (4,6) это форма выхода. Если размеры не верны, мы в конечном итоге читаем значения мусора. Это потому, что мы получаем доступ к данным, перемещая указатель на пару байтов (в зависимости от типа данных).

Определение правильного значения шагов важно для получения ожидаемых результатов. Перед вычислением шагов выясните, arr.strides[-1] памяти занято каждым элементом, используя arr.strides[-1]. В этом примере память, занимаемая одним элементом, составляет 4 байта. Numpy массивы создаются в ряду основных моды. Первый элемент следующей строки находится рядом с последним элементом текущей строки.

Пример: 0, 1 | 10, 11 |...

10 прямо рядом с 1.

Представьте, что двумерный массив преобразован в 1D (это допустимо, поскольку данные хранятся в основном формате строки). Первый элемент каждой строки в выходных данных является нечетным индексированным элементом в одномерном массиве. 0, 10, 20, 30,..

Следовательно, количество шагов в памяти, которое нам нужно сделать, чтобы перейти от 0 до 10, от 10 до 20 и т.д., Составляет 2 * mem размер элемента. Каждая строка имеет шаг 2 * 4 байта = 8. Для данной строки в выводе все элементы смежны друг с другом в нашем воображаемом одномерном массиве. Чтобы получить следующий элемент в строке, просто сделайте один шаг, равный размеру элемента. Значение шага столбца составляет 4 байта.

Следовательно, strides=(8,4)

Альтернативное объяснение: выход имеет форму (4,6). Колонна шаг 4. Итак, элементы первой строки начинаются с индекса 0 и имеют 6 элементов, каждый из которых расположен на расстоянии 4 байта. После того, как первая строка собрана, вторая строка начинается на 8 байт от начала текущей строки. Третий ряд начинается на 8 байт от начальной точки второго ряда и так далее.

Shape определяет количество нужных нам строк и столбцов. шаги определяют шаги памяти, чтобы начать строку и собрать элемент столбца

Ответ 4

more_itertools.windowed короткого списка возможно с more_itertools.windowed 1:

Дано

import numpy as np
import more_itertools as mit


a = [["00","01"],
     ["10","11"],
     ["20","21"],
     ["30","31"],
     ["40","41"],
     ["50","51"]]

b = np.array(a)

Код

np.array([list(mit.flatten(w)) for w in mit.windowed(a, n=3)])

или же

np.array([[i for item in w for i in item] for w in mit.windowed(a, n=3)])

или же

np.array(list(mit.windowed(b.ravel(), n=6)))

Выход

array([['00', '01', '10', '11', '20', '21'],
       ['10', '11', '20', '21', '30', '31'],
       ['20', '21', '30', '31', '40', '41'],
       ['30', '31', '40', '41', '50', '51']], 
      dtype='<U2')

Раздвижные окна размером n=3 созданы и сплющены. Обратите внимание, что размер шага по умолчанию - more_itertools.windowed(..., step=1).


Спектакль

В качестве массива принятый ответ является самым быстрым.

%timeit np.hstack((a[:-2], a[1:-1], a[2:]))
# 37.5 µs ± 1.88 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit np.hstack((b[:-2], b[1:-1], b[2:]))
# 12.9 µs ± 166 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

%timeit np.array([list(mit.flatten(w)) for w in mit.windowed(a, n=3)])
# 23.2 µs ± 1.73 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit np.array([[i for item in w for i in item] for w in mit.windowed(a, n=3)])
# 21.2 µs ± 999 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit np.array(list(mit.windowed(b.ravel(), n=6)))
# 43.4 µs ± 374 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Сторонняя библиотека, которая реализует рецепты itertool и множество полезных инструментов.

Ответ 5

Это чистая реализация Python:

def sliding_window(arr, window=3):
    i = iter(arr)
    a = []
    for e in range(0, window): a.append(next(i))
    yield a
    for e in i:
        a = a[1:] + [e]
        yield a

Пример:

# flatten array
flatten = lambda l: [item for sublist in l for item in sublist]

a = [[0,1], [10,11], [20,21], [30,31], [40,41], [50,51]]
w = sliding_window(a, width=3)
print( list(map(flatten,w)) )

[[0, 1, 10, 11, 20, 21], [10, 11, 20, 21, 30, 31], [20, 21, 30, 31, 40, 41], [30, 31, 40, 41, 50, 51]]

Benchmark

import timeit
def benchmark():
  a = [[0,1], [10,11], [20,21], [30,31], [40,41], [50,51]]
  sliding_window(a, width=3)

times = timeit.Timer(benchmark).repeat(3, number=1000)
time_taken = min(times) / 1000
print(time_taken)

1.0944640007437556e-06

Ответ 6

Вот одна строка с использованием Numpy> = v1.17

splits = np.vstack(np.split(x,np.array([[i, i+3] for i in range(x.shape[0] - x.shape[1])]).reshape(-1))).reshape(-1, 6) 

Test

x = np.array([[00,1],
              [10,11],
              [20,21],
              [30,31],
              [40,41],
              [50,51]])

Результат

[[ 0  1 10 11 20 21]
 [10 11 20 21 30 31]
 [20 21 30 31 40 41]
 [30 31 40 41 50 51]]

Тест производительности на большом массиве

import numpy as np
import time

x = np.array(range(1000)).reshape(-1, 2)

all_t = 0.
for i in range(1000):
    start_ = time.time()
    np.vstack(
        numpy.split(x,np.array([[i, i+3] for i in range(x.shape[0] - x.shape[1])])
                    .reshape(-1))).reshape(-1, 6)
    all_t += time.time() - start_

print('Average Time of 1000 Iterations on Array of Shape '
      '1000 x 2 is: {} Seconds.'.format(all_t/1000.))

Результат выступления

Average Time of 1000 Iterations on Array of Shape 1000 x 2 is: 0.0016909 Seconds.