Раздвижное окно формы 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 аргумента.
- данные
- форма
- успехи
Данные это массив, на котором мы будем работать.
Чтобы использовать 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.