Эта статья продолжает цикл примеров numpy-программирования: первая статья и вторая. Рекомендуем вам ознакомится с этими статьями, надеюсь в них вы найдете ответы на многие вопросы. Сегодня же мы решим задачу скользящего окна.
Что такое скользящее окно? Очень часто приходится использовать статистические функции, например, скользящее среднее, не для всего ряда, а за определенный период, который называется размером окна, смещая окно на шаг смещения (обычно на единицу), мы можем получить желаемые данные по всему ряду. Давайте, как всегда, поясним суть задачи на примере. Пусть у нас есть самый простой и обычный вектор: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]. Размер окна 5, смещение 1, тогда матрица скользящего окна будет выглядеть так:
Алгоритм в классическом стиле выглядит так:
Вся суть алгоритма в последней строке, которая представляет собой list comprehension, который генерирует необходимую нам матрицу. Каждая строка новой матрицы представляет собой скользящий срез исходного вектора по размеру окна. Неочевидная часть заключается в том, сколько раз мы можем пройтись по исходной матрице. Последним индексом, который уместит окно, будет индекс равный размеру исходного вектора минус размер окна плюс 1. Казалось бы на этом можно и закончить статью, но матричное программирование "не признает" list comprehension, потому что этот прием, как ни крути, все-таки цикл.
Если в прошлый раз мы отмечали читаемость нового подхода, то в этот раз он будет мозголомным. Начнем, пожалуй, с того факта, что у нас есть возможность преобразовать исходный массив в выходной с помощью индексов. Для решения нашей задачи достаточно иметь следующую матрицу индексов:
То есть, если исходный вектор data, а вышеуказанной матрице присвоено имя idx, то решением нашей задачи будет data[idx]. Но постойте, матрица индексов полностью идентична итоговой матрице. Это всего лишь частный случай. Если мы поменяем значения в исходном векторе, то матрица индексов останется такой же.
Нам осталось получить матрицу индексов. Тут мы воспользуемся неочевидной возможностью матричного сложения. Для этого нам потребуется слагаемое строки и слагаемое столбца, которые представляют собой первую строку и первый столбец матрицы индексов. Получим первое слагаемое:
По причинам, которые будут понятны позже, нам нужен двумерный массив, полученный из вектора, сгенерированного функцией arange длиной равной длине окна. Нулевая ось указывает на двумерный массив по горизонтали (см. матрицу индексов).
Для получения второго слагаемого нам надо вычислить "высоту", то есть количество строк, которые мы уже вычисляли в классическом варианте. Дальше похоже на первое слагаемое:
Второе слагаемое должно быть расположено по вертикали, поэтому axis=1:
Теперь можем получить матрицу индексов, просто сложив два слагаемых, не смотря на разную размерность слагаемых. Соединим все наши рассуждения в виде одной функции:
В первой строке мы делаем принудительное преобразование входящего массива в numpy-массив, чтобы можно было передавать и обычные списки в качестве первого параметра функции. Остальные моменты были рассмотрены выше.
С виду функция не сложная, но понять её можно только разбив на составляющие. Всю функцию можно вытянуть в одну строку, тогда её будет еще сложнее читать. Самый интересный момент в этом примере: сложение матриц разной формы. Первое слагаемое имеет размерность (1, 5), второе - (6, 1), а сумма - (6, 5). Чтобы понять некоторые вещи, нужно с этим переспать.
В качестве домашнего задания подумайте, как переделать функцию, чтобы задействовать еще один параметр - step (шаг смещения).