Вычисление локальных средств в массиве 1D numpy

У меня есть 1D NumPy массив следующим образом:

import numpy as np
d = np.array([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20])

Я хочу рассчитать средства (1,2,6,7), (3,4,8,9) и т.д. Это включает в себя среднее из 4 элементов: два последовательных элемента и два последовательных элемента 5 позиций после.

Я попробовал следующее:

>> import scipy.ndimage.filters as filt
>> res = filt.uniform_filter(d,size=4)
>> print res
[ 1  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19]

Это, к сожалению, не дает мне желаемых результатов. Как я могу это сделать?

Ответ 1

Вместо индексации вы можете приблизиться к этому с точки зрения обработки сигналов. Вы в основном выполняете дискретную свертку вашего входного сигнала с ядром с 7 кранами, где три коэффициента центра равны 0, а конечности равны 1, и поскольку вы хотите вычислить среднее значение, вам нужно умножить все значения на (1/4). Однако вы не вычисляете свертку всех элементов, но мы рассмотрим это позже. Один из способов - scipy.ndimage.filters.convolve1d:

import numpy as np
from scipy.ndimage import filters
d = np.arange(1, 21, dtype=np.float)
ker = (1.0/4.0)*np.array([1,1,0,0,0,1,1], dtype=np.float)
out = filters.convolve1d(d, ker)[3:-3:2]

Поскольку вы используете ядро ​​с 7 кранами, свертка расширяет вывод на 3 влево и 3 вправо, поэтому вам нужно будет вырезать первый и последний три элемента. Вы также хотите пропустить каждый другой элемент, потому что свертка включает скользящее окно, но вы хотите отбросить каждый другой элемент, чтобы получить желаемый результат.

Мы получаем это для out:

In [47]: out
Out[47]: array([  4.,   6.,   8.,  10.,  12.,  14.,  16.])

Чтобы дважды проверить, есть ли у нас правильный результат, попробуйте выполнить некоторые выборочные вычисления для каждого элемента. Первый элемент равен (1+2+6+7)/4 = 4. Второй элемент равен (3+4+8+9)/4 = 6 и т.д.


Для решения с меньшими головными болями, попробуйте numpy.convolve с флагом mode=valid. Это позволяет избежать вырезания дополнительного дополнения влево и вправо, но вам все равно нужно пропустить все остальные элементы:

import numpy as np
d = np.arange(1, 21, dtype=np.float)
ker = (1.0/4.0)*np.array([1,1,0,0,0,1,1], dtype=np.float)
out = np.convolve(d, ker, mode='valid')[::2]

Мы также получаем:

In [59]: out
Out[59]: array([  4.,   6.,   8.,  10.,  12.,  14.,  16.])

Наконец, если вы хотите индексировать, может быть что-то вроде этого:

length = len(d[6::2])
out = np.array([(a+b+c+e)/4.0 for (a,b,c,e) in zip(d[::2][:length], d[1::2][:length], d[5::2][:length], d[6::2])])

Получаем:

In [69]: out
Out[69]: array([  4.,   6.,   8.,  10.,  12.,  14.,  16.])

Это действительно уродливо, но оно работает. Общая длина вашего сигнала определяется тем, что конец каждого окна находится в 7-м индексе. Длина этого массива, который содержит эти индексы, определяет конечную длину вашего сигнала. Также обратите внимание, что для элемента в окне его следующий элемент можно найти, пропуская все остальные элементы до конца массива. Всего 4 из этих последовательностей, и мы просто zip над этими 4 последовательностями, где каждая последовательность пропускает каждый другой элемент, но есть смещение, с которого мы начинаем. Первая последовательность начинается со смещения 0, следующего в 1, следующего в 5 и следующего в 6. Мы собираем эти четыре элемента и усредняем их, а затем пропускаем все в массиве до тех пор, пока не закончим.

Кстати, мне все еще нравится свертка.

Ответ 2

Вы можете использовать numpy.lib.stride_tricks.as_strided() для получения массива группировки, применимого для более общего случая:

import numpy as np
from numpy.lib.stride_tricks import as_strided

d = np.arange(1, 21)

consec = 2
offset = 5
nsub = 2
pace = 2

s = d.strides[0]
ngroups= (d.shape[0] - (consec + (nsub-1)*offset - 1))//pace
a = as_strided(d, shape=(ngroups, nsub, consec),
               strides=(pace*s, offset*s, 1*s))

Где:

  • consec - количество последовательных чисел в подгруппе
  • offset смещение между первой записью в каждой подгруппе
  • nsub количество подгрупп (1, 2 - одна подгруппа, отделенная от второй подгруппы 6, 7 на offset
  • pace указывает шаг между первым входом двух групп, который в вашем случае pace=consec, но может быть другим в более общем случае

В вашем случае (с использованием заданных значений) a будет:

array([[[ 1,  2],
        [ 6,  7]],

       [[ 3,  4],
        [ 8,  9]],

       [[ 5,  6],
        [10, 11]],

       [[ 7,  8],
        [12, 13]],

       [[ 9, 10],
        [14, 15]],

       [[11, 12],
        [16, 17]],

       [[13, 14],
        [18, 19]]])

Отсюда, когда он готов к получению желаемого среднего, сделав:

a.mean(axis=-1).mean(axis=-1)

#array([  4.,   6.,   8.,  10.,  12.,  14.,  16.])