Как рассчитать скользящую среднюю с помощью NumPy?


111

Кажется, нет функции, которая просто вычисляет скользящее среднее на numpy / scipy, что приводит к запутанным решениям .

У меня двоякий вопрос:

  • Какой самый простой способ (правильно) реализовать скользящую среднюю с помощью numpy?
  • Поскольку это кажется нетривиальным и подверженным ошибкам, есть ли веская причина не включать батареи в этом случае?

19
Решение свертки не кажется мне таким запутанным!
wim

4
Разве скользящее среднее не является просто фильтром нижних частот (т.е. «размытием»)? Уверен, что это именно та свертка, для которой предназначена ...
user541686

@mmgp Думаю, я надеялся ошибиться, или что на то была веская, очевидная причина.
goncalopp

3
@wim Это было наполовину означало каламбур. Но сам факт существования вопроса означает, что создать скользящую среднюю из numpy.convolute непросто.
goncalopp

Ответы:


167

Если вы просто хотите простой Невзвешенный скользящей средней, вы можете легко реализовать его np.cumsum, что может быть есть методы быстрее , чем БПФ на основе:

РЕДАКТИРОВАТЬ Исправлено неправильное индексирование, обнаруженное Бином в коде. РЕДАКТИРОВАТЬ

def moving_average(a, n=3) :
    ret = np.cumsum(a, dtype=float)
    ret[n:] = ret[n:] - ret[:-n]
    return ret[n - 1:] / n

>>> a = np.arange(20)
>>> moving_average(a)
array([  1.,   2.,   3.,   4.,   5.,   6.,   7.,   8.,   9.,  10.,  11.,
        12.,  13.,  14.,  15.,  16.,  17.,  18.])
>>> moving_average(a, n=4)
array([  1.5,   2.5,   3.5,   4.5,   5.5,   6.5,   7.5,   8.5,   9.5,
        10.5,  11.5,  12.5,  13.5,  14.5,  15.5,  16.5,  17.5])

Итак, я предполагаю, что ответ таков: это действительно легко реализовать, и, возможно, numpy уже немного раздут со специализированной функциональностью.


11
Этот код неверен. например, moving_average ([1,2,5,10], n = 2) дает [1., 3.5, 8.5]. Даже тестовый пример ответчика для скользящего среднего значений от 0 до 19 неверен, утверждая, что среднее значение 0, 1 и 2 равно 0,5. Как он получил 6 голосов?
JeremyKun

2
Спасибо за проверку ошибок, теперь вроде все работает нормально. Что касается голосов «за», я предполагаю, что общая идея ответа была взвешена более серьезно, чем единичная ошибка в реализации, но кто знает.
Хайме

3
Я нашел проблему. ret[n:] -= ret[:-n]НЕ ТАК ЖЕ как ret[n:] = ret[n:] - ret[:-n]. Я исправил код в этом ответе. Изменить: Нет, кто-то другой просто опередил меня.
Timmmm

8
@Timmmm Я сделал, это действительно была проблема. Общий принцип, лежащий в основе этого ответа, широко используется при обработке изображений (они называют это таблицами суммированных площадей), поэтому проблема должна была быть в реализации. Хороший пример преждевременной оптимизации, поскольку я как бы вспоминаю выполнение операции на месте, «потому что это будет более эффективно». С другой стороны, он, вероятно, быстрее
дал

44
Хммм, кажется, что с этой «простой в реализации» функцией на самом деле довольно легко ошибиться, и она вызвала хорошее обсуждение эффективности памяти. Я счастлив иметь вздутие живота, если это означает, что я знаю, что что-то было сделано правильно.
Ричард

81

Отсутствие в NumPy конкретной функции, зависящей от предметной области, возможно, связано с дисциплиной и верностью основной команды NumPy основной директиве: предоставить тип N-мерного массива , а также функции для создания и индексации этих массивов. Как и многие другие основные задачи, эта не маленькая, и NumPy справляется с ней блестяще.

(Намного) больший SciPy содержит гораздо большую коллекцию предметно-ориентированных библиотек ( которые разработчики SciPy называют подпакетами ) - например, численная оптимизация ( оптимизация ), обработка сигналов ( сигнал ) и интегральное исчисление ( интегрирование ).

Я предполагаю, что функция, которую вы ищете, находится по крайней мере в одном из подпакетов SciPy ( возможно, scipy.signal ); тем не менее, я бы сначала посмотрел на коллекцию Scikit SciPy , определил соответствующий scikit (ы) и поищу там интересующую функцию.

Scikits - это независимо разработанные пакеты, основанные на NumPy / SciPy и предназначенные для определенной технической дисциплины (например, scikits-image , scikits-learn и т. Д.). Некоторые из них (в частности, потрясающий OpenOpt для числовой оптимизации) получили высокую оценку, зрелые проекты задолго до того, как они решили жить под относительно новой рубрикой scikits . На домашней странице Scikits указано около 30 таких scikits. , хотя по крайней мере некоторые из них больше не находятся в активной разработке.

Следование этому совету приведет вас к scikits-timeseries ; однако этот пакет больше не находится в активной разработке; Фактически, Pandas стал, AFAIK, де-факто библиотекой временных рядов, основанной на NumPy .

В Pandas есть несколько функций, которые можно использовать для расчета скользящей средней ; Самым простым из них, вероятно, является Rolling_mean , который вы используете так:

>>> # the recommended syntax to import pandas
>>> import pandas as PD
>>> import numpy as NP

>>> # prepare some fake data:
>>> # the date-time indices:
>>> t = PD.date_range('1/1/2010', '12/31/2012', freq='D')

>>> # the data:
>>> x = NP.arange(0, t.shape[0])

>>> # combine the data & index into a Pandas 'Series' object
>>> D = PD.Series(x, t)

Теперь просто вызовите функцию Rolling_mean, передав объект Series и размер окна , который в моем примере ниже составляет 10 дней .

>>> d_mva = PD.rolling_mean(D, 10)

>>> # d_mva is the same size as the original Series
>>> d_mva.shape
    (1096,)

>>> # though obviously the first w values are NaN where w is the window size
>>> d_mva[:3]
    2010-01-01         NaN
    2010-01-02         NaN
    2010-01-03         NaN

убедитесь, что это сработало - например, сравните значения 10-15 в исходной серии с новой серией, сглаженной скользящим средним

>>> D[10:15]
     2010-01-11    2.041076
     2010-01-12    2.041076
     2010-01-13    2.720585
     2010-01-14    2.720585
     2010-01-15    3.656987
     Freq: D

>>> d_mva[10:20]
      2010-01-11    3.131125
      2010-01-12    3.035232
      2010-01-13    2.923144
      2010-01-14    2.811055
      2010-01-15    2.785824
      Freq: D

Функция Rolling_mean вместе с примерно дюжиной других функций неофициально сгруппированы в документации Pandas под рубрикой « подвижные оконные функции»; Вторая связанная группа функций в Pandas называется экспоненциально взвешенными функциями (например, ewma , которая вычисляет экспоненциально скользящее средневзвешенное значение). Тот факт, что эта вторая группа не включена в первую ( функции движущегося окна ), возможно, объясняется тем, что экспоненциально взвешенные преобразования не полагаются на окно фиксированной длины.


6
У Pandas есть сильная линейка движущихся оконных функций. Но мне кажется, что это слишком большие накладные расходы для простой скользящей средней.
Jaime

6
Что ж, я сомневаюсь, что расчет скользящей средней - это отдельное требование для OP или для кого-то еще. Если вам нужно рассчитать скользящее среднее, то у вас почти наверняка есть временной ряд, а это означает, что вам нужна структура данных, которая позволяет согласовывать индекс даты и времени с вашими данными, и это «накладные расходы», на которые вы ссылаетесь.
doug

2
Во-первых, спасибо, что нашли время написать этот чрезвычайно информативный ответ. В самом деле, я не вижу применения скользящей средней, не связанной с временными рядами. Но это не значит, что нужно согласовывать его с
датой и временем

3
Просто хотел добавить, что функция скользящего среднего была извлечена в библиотеку Bottleneck, если pandas кажется слишком тяжелой как зависимость.
robochat

4
'Rolling_mean' больше не является частью pandas, пожалуйста, см. ответ, используя вместо этого '
Rolling

66

Простой способ добиться этого - использовать np.convolve. Идея состоит в том, чтобы использовать способ вычисления дискретной свертки и использовать ее для получения скользящего среднего . Это можно сделать, свернув последовательность np.onesс длиной, равной длине скользящего окна, которую мы хотим.

Для этого мы могли бы определить следующую функцию:

def moving_average(x, w):
    return np.convolve(x, np.ones(w), 'valid') / w

Эта функция будет принимать свертку последовательности xи последовательность единиц длины w. Обратите внимание , что выбранный modeявляется validтаким , что свертка произведение задается только для точек , где последовательности перекрываются полностью.


Некоторые примеры:

x = np.array([5,3,8,10,2,1,5,1,0,2])

Для скользящей средней с окном длины 2:

moving_average(x, 2)
# array([4. , 5.5, 9. , 6. , 1.5, 3. , 3. , 0.5, 1. ])

А для окна длиной 4:

moving_average(x, 4)
# array([6.5 , 5.75, 5.25, 4.5 , 2.25, 1.75, 2.  ])

Как convolveработает?

Давайте более подробно рассмотрим способ вычисления дискретной свертки. Следующая функция предназначена для воспроизведения способа np.convolveвычисления выходных значений:

def mov_avg(x, w):
    for m in range(len(x)-(w-1)):
        yield sum(np.ones(w) * x[m:m+w]) / w 

Что для того же примера выше также даст:

list(mov_avg(x, 2))
# [4.0, 5.5, 9.0, 6.0, 1.5, 3.0, 3.0, 0.5, 1.0]

Итак, что делается на каждом шаге, так это переместить внутренний продукт между массивом единиц и текущим окном . В этом случае умножение на np.ones(w)является излишним, поскольку мы напрямую берем sumпоследовательность.

Ниже приведен пример того, как вычисляются первые результаты, чтобы он был немного яснее. Предположим, нам нужно окно w=4:

[1,1,1,1]
[5,3,8,10,2,1,5,1,0,2]
= (1*5 + 1*3 + 1*8 + 1*10) / w = 6.5

И следующий результат будет вычислен как:

  [1,1,1,1]
[5,3,8,10,2,1,5,1,0,2]
= (1*3 + 1*8 + 1*10 + 1*2) / w = 5.75

И так далее, возвращая скользящее среднее значение последовательности после выполнения всех перекрытий.


1
Замечательная идея! Это быстрее, чем ответ @Jaime для малых n, но становится медленнее для больших n.
Фелипе Жерар

Спасибо @FelipeGerard! Да , как указано в комментариях, в то время как этот подход может быть , не может быть столь же эффективным , как некоторые другие решения Numpy, имо приятно иметь в качестве альтернативы для посетителей в будущем , учитывая их простоту и лаконичность
Yatu

1
Иногда бывает полезно иметь выходной массив того же размера, что и входной. Для этого mode='valid'можно заменить на 'same'. Как раз в этом случае крайние точки будут стремиться к нулю.
Илья Бараховский,

В ситуации, когда некоторые элементы массива «x» функции могут иметь значение None или ноль, как получить соответствующие значения «x» возвращенных значений из этой функции? Размер массива, возвращаемого этой функцией, может быть меньше переданного ей массива 'x'.
Sun Bear

17

Вот несколько способов сделать это, а также некоторые тесты. Лучшие методы - это версии, использующие оптимизированный код из других библиотек. Этот bottleneck.move_meanметод, наверное, самый лучший. Этот scipy.convolveподход также очень быстрый, расширяемый, синтаксически и концептуально простой, но плохо масштабируется для очень больших значений окна. numpy.cumsumМетод хорош , если вам нужен чистый numpyподход.

Примечание. Некоторые из них (например bottleneck.move_mean) не центрированы и будут сдвигать ваши данные.

import numpy as np
import scipy as sci
import scipy.signal as sig
import pandas as pd
import bottleneck as bn
import time as time

def rollavg_direct(a,n): 
    'Direct "for" loop'
    assert n%2==1
    b = a*0.0
    for i in range(len(a)) :
        b[i]=a[max(i-n//2,0):min(i+n//2+1,len(a))].mean()
    return b

def rollavg_comprehension(a,n):
    'List comprehension'
    assert n%2==1
    r,N = int(n/2),len(a)
    return np.array([a[max(i-r,0):min(i+r+1,N)].mean() for i in range(N)]) 

def rollavg_convolve(a,n):
    'scipy.convolve'
    assert n%2==1
    return sci.convolve(a,np.ones(n,dtype='float')/n, 'same')[n//2:-n//2+1]  

def rollavg_convolve_edges(a,n):
    'scipy.convolve, edge handling'
    assert n%2==1
    return sci.convolve(a,np.ones(n,dtype='float'), 'same')/sci.convolve(np.ones(len(a)),np.ones(n), 'same')  

def rollavg_cumsum(a,n):
    'numpy.cumsum'
    assert n%2==1
    cumsum_vec = np.cumsum(np.insert(a, 0, 0)) 
    return (cumsum_vec[n:] - cumsum_vec[:-n]) / n

def rollavg_cumsum_edges(a,n):
    'numpy.cumsum, edge handling'
    assert n%2==1
    N = len(a)
    cumsum_vec = np.cumsum(np.insert(np.pad(a,(n-1,n-1),'constant'), 0, 0)) 
    d = np.hstack((np.arange(n//2+1,n),np.ones(N-n)*n,np.arange(n,n//2,-1)))  
    return (cumsum_vec[n+n//2:-n//2+1] - cumsum_vec[n//2:-n-n//2]) / d

def rollavg_roll(a,n):
    'Numpy array rolling'
    assert n%2==1
    N = len(a)
    rolling_idx = np.mod((N-1)*np.arange(n)[:,None] + np.arange(N), N)
    return a[rolling_idx].mean(axis=0)[n-1:] 

def rollavg_roll_edges(a,n):
    # see /programming/42101082/fast-numpy-roll
    'Numpy array rolling, edge handling'
    assert n%2==1
    a = np.pad(a,(0,n-1-n//2), 'constant')*np.ones(n)[:,None]
    m = a.shape[1]
    idx = np.mod((m-1)*np.arange(n)[:,None] + np.arange(m), m) # Rolling index
    out = a[np.arange(-n//2,n//2)[:,None], idx]
    d = np.hstack((np.arange(1,n),np.ones(m-2*n+1+n//2)*n,np.arange(n,n//2,-1)))
    return (out.sum(axis=0)/d)[n//2:]

def rollavg_pandas(a,n):
    'Pandas rolling average'
    return pd.DataFrame(a).rolling(n, center=True, min_periods=1).mean().to_numpy()

def rollavg_bottlneck(a,n):
    'bottleneck.move_mean'
    return bn.move_mean(a, window=n, min_count=1)

N = 10**6
a = np.random.rand(N)
functions = [rollavg_direct, rollavg_comprehension, rollavg_convolve, 
        rollavg_convolve_edges, rollavg_cumsum, rollavg_cumsum_edges, 
        rollavg_pandas, rollavg_bottlneck, rollavg_roll, rollavg_roll_edges]

print('Small window (n=3)')
%load_ext memory_profiler
for f in functions : 
    print('\n'+f.__doc__+ ' : ')
    %timeit b=f(a,3)

print('\nLarge window (n=1001)')
for f in functions[0:-2] : 
    print('\n'+f.__doc__+ ' : ')
    %timeit b=f(a,1001)

print('\nMemory\n')
print('Small window (n=3)')
N = 10**7
a = np.random.rand(N)
%load_ext memory_profiler
for f in functions[2:] : 
    print('\n'+f.__doc__+ ' : ')
    %memit b=f(a,3)

print('\nLarge window (n=1001)')
for f in functions[2:-2] : 
    print('\n'+f.__doc__+ ' : ')
    %memit b=f(a,1001)

Время, маленькое окно (n = 3)

Direct "for" loop : 

4.14 s ± 23.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

List comprehension : 
3.96 s ± 27.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

scipy.convolve : 
1.07 ms ± 26.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

scipy.convolve, edge handling : 
4.68 ms ± 9.69 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

numpy.cumsum : 
5.31 ms ± 5.11 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

numpy.cumsum, edge handling : 
8.52 ms ± 11.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Pandas rolling average : 
9.85 ms ± 9.63 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

bottleneck.move_mean : 
1.3 ms ± 12.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Numpy array rolling : 
31.3 ms ± 91.9 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Numpy array rolling, edge handling : 
61.1 ms ± 55.9 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Время, большое окно (n = 1001)

Direct "for" loop : 
4.67 s ± 34 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

List comprehension : 
4.46 s ± 14.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

scipy.convolve : 
103 ms ± 165 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

scipy.convolve, edge handling : 
272 ms ± 1.23 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

numpy.cumsum : 
5.19 ms ± 12.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

numpy.cumsum, edge handling : 
8.7 ms ± 11.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Pandas rolling average : 
9.67 ms ± 199 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

bottleneck.move_mean : 
1.31 ms ± 15.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Память, маленькое окно (n = 3)

The memory_profiler extension is already loaded. To reload it, use:
  %reload_ext memory_profiler

scipy.convolve : 
peak memory: 362.66 MiB, increment: 73.61 MiB

scipy.convolve, edge handling : 
peak memory: 510.24 MiB, increment: 221.19 MiB

numpy.cumsum : 
peak memory: 441.81 MiB, increment: 152.76 MiB

numpy.cumsum, edge handling : 
peak memory: 518.14 MiB, increment: 228.84 MiB

Pandas rolling average : 
peak memory: 449.34 MiB, increment: 160.02 MiB

bottleneck.move_mean : 
peak memory: 374.17 MiB, increment: 75.54 MiB

Numpy array rolling : 
peak memory: 661.29 MiB, increment: 362.65 MiB

Numpy array rolling, edge handling : 
peak memory: 1111.25 MiB, increment: 812.61 MiB

Память, большое окно (n = 1001)

scipy.convolve : 
peak memory: 370.62 MiB, increment: 71.83 MiB

scipy.convolve, edge handling : 
peak memory: 521.98 MiB, increment: 223.18 MiB

numpy.cumsum : 
peak memory: 451.32 MiB, increment: 152.52 MiB

numpy.cumsum, edge handling : 
peak memory: 527.51 MiB, increment: 228.71 MiB

Pandas rolling average : 
peak memory: 451.25 MiB, increment: 152.50 MiB

bottleneck.move_mean : 
peak memory: 374.64 MiB, increment: 75.85 MiB

11

Этот ответ с использованием Pandas адаптирован сверху, поскольку rolling_meanбольше не является частью Pandas

# the recommended syntax to import pandas
import pandas as pd
import numpy as np

# prepare some fake data:
# the date-time indices:
t = pd.date_range('1/1/2010', '12/31/2012', freq='D')

# the data:
x = np.arange(0, t.shape[0])

# combine the data & index into a Pandas 'Series' object
D = pd.Series(x, t)

Теперь просто вызовите функцию rollingв фрейме данных с размером окна, который в моем примере ниже составляет 10 дней.

d_mva10 = D.rolling(10).mean()

# d_mva is the same size as the original Series
# though obviously the first w values are NaN where w is the window size
d_mva10[:11]

2010-01-01    NaN
2010-01-02    NaN
2010-01-03    NaN
2010-01-04    NaN
2010-01-05    NaN
2010-01-06    NaN
2010-01-07    NaN
2010-01-08    NaN
2010-01-09    NaN
2010-01-10    4.5
2010-01-11    5.5
Freq: D, dtype: float64

5

Я считаю, что это легко решить с помощью узкого места

См. Базовый образец ниже:

import numpy as np
import bottleneck as bn

a = np.random.randint(4, 1000, size=(5, 7))
mm = bn.move_mean(a, window=2, min_count=1)

Это дает среднее значение перемещения по каждой оси.

  • «мм» - скользящее среднее для «а».

  • «окно» - это максимальное количество записей, которые следует учитывать для скользящего среднего.

  • «min_count» - это минимальное количество записей, которые следует учитывать для скользящего среднего (например, для первого элемента или если массив имеет значения nan).

Хорошая часть заключается в том, что «Узкое место» помогает справиться со значениями наночастиц, а также очень эффективно.


2

В случае, если вы хотите тщательно позаботиться о краевых условиях ( вычислить среднее значение только из доступных элементов на краях ), следующая функция поможет.

import numpy as np

def running_mean(x, N):
    out = np.zeros_like(x, dtype=np.float64)
    dim_len = x.shape[0]
    for i in range(dim_len):
        if N%2 == 0:
            a, b = i - (N-1)//2, i + (N-1)//2 + 2
        else:
            a, b = i - (N-1)//2, i + (N-1)//2 + 1

        #cap indices to min and max indices
        a = max(0, a)
        b = min(dim_len, b)
        out[i] = np.mean(x[a:b])
    return out

>>> running_mean(np.array([1,2,3,4]), 2)
array([1.5, 2.5, 3.5, 4. ])

>>> running_mean(np.array([1,2,3,4]), 3)
array([1.5, 2. , 3. , 3.5])

1
for i in range(len(Data)):
    Data[i, 1] = Data[i-lookback:i, 0].sum() / lookback

Попробуйте этот фрагмент кода. Я думаю, что это проще и работает. Lookback - это окно скользящей средней.

В поле Data[i-lookback:i, 0].sum()я указал 0для ссылки на первый столбец набора данных, но вы можете поместить любой столбец, который вам нравится, если у вас более одного столбца.


0

На самом деле мне хотелось немного другого поведения, чем принятый ответ. Я создавал средство извлечения скользящего среднего для sklearnконвейера, поэтому мне потребовалось, чтобы выходные данные скользящего среднего имели тот же размер, что и входные. Я хочу, чтобы скользящая средняя предполагала, что серия остается постоянной, то есть скользящая средняя [1,2,3,4,5]с окном 2 даст[1.5,2.5,3.5,4.5,5.0] .

Для векторов-столбцов (мой вариант использования) мы получаем

def moving_average_col(X, n):
  z2 = np.cumsum(np.pad(X, ((n,0),(0,0)), 'constant', constant_values=0), axis=0)
  z1 = np.cumsum(np.pad(X, ((0,n),(0,0)), 'constant', constant_values=X[-1]), axis=0)
  return (z1-z2)[(n-1):-1]/n

А для массивов

def moving_average_array(X, n):
  z2 = np.cumsum(np.pad(X, (n,0), 'constant', constant_values=0))
  z1 = np.cumsum(np.pad(X, (0,n), 'constant', constant_values=X[-1]))
  return (z1-z2)[(n-1):-1]/n

Конечно, не обязательно принимать постоянные значения для заполнения, но в большинстве случаев этого должно быть достаточно.


0

Talib содержит простой инструмент скользящего среднего, а также другие аналогичные инструменты усреднения (например, экспоненциальное скользящее среднее). Ниже сравнивается метод с некоторыми другими решениями.


%timeit pd.Series(np.arange(100000)).rolling(3).mean()
2.53 ms ± 40.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit talib.SMA(real = np.arange(100000.), timeperiod = 3)
348 µs ± 3.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit moving_average(np.arange(100000))
638 µs ± 45.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Одно предостережение заключается в том, что в настоящем должны быть элементы dtype = float. В противном случае возникает следующая ошибка

Исключение: настоящий не двойной


0

Вот быстрая реализация с использованием numba (обратите внимание на типы). Обратите внимание, что там со смещением есть nans.

import numpy as np
import numba as nb

@nb.jit(nb.float64[:](nb.float64[:],nb.int64),
        fastmath=True,nopython=True)
def moving_average( array, window ):    
    ret = np.cumsum(array)
    ret[window:] = ret[window:] - ret[:-window]
    ma = ret[window - 1:] / window
    n = np.empty(window-1); n.fill(np.nan)
    return np.concatenate((n.ravel(), ma.ravel())) 

Это возвращает nans в начале.
Адам Эриксон

0

скользящая средняя

  • переверните массив в i и просто возьмите среднее значение от i до n.

  • используйте понимание списка для создания мини-массивов на лету.

x = np.random.randint(10, size=20)

def moving_average(arr, n):
    return [ (arr[:i+1][::-1][:n]).mean() for i, ele in enumerate(arr) ]
n = 5

moving_average(x, n)

0

Я использую либо решение принятого ответа , слегка измененное, чтобы иметь ту же длину для вывода, что и ввод, либо pandasверсию, указанную в комментарии к другому ответу. Я резюмирую их здесь с помощью воспроизводимого примера для использования в будущем:

import numpy as np
import pandas as pd

def moving_average(a, n):
    ret = np.cumsum(a, dtype=float)
    ret[n:] = ret[n:] - ret[:-n]
    return ret / n

def moving_average_centered(a, n):
    return pd.Series(a).rolling(window=n, center=True).mean().to_numpy()

A = [0, 0, 1, 2, 4, 5, 4]
print(moving_average(A, 3))    
# [0.         0.         0.33333333 1.         2.33333333 3.66666667 4.33333333]
print(moving_average_centered(A, 3))
# [nan        0.33333333 1.         2.33333333 3.66666667 4.33333333 nan       ]

0

Сравнивая решение ниже с тем, которое использует cumsum of numpy, это занимает почти половину времени . Это связано с тем, что не нужно проходить через весь массив, чтобы произвести суммирование, а затем выполнять все вычитание. Более того, cumsum может быть « опасным », если массив огромен, а число огромно ( возможно переполнение ). Конечно, и здесь опасность существует, но по крайней мере суммируются только существенные числа.

def moving_average(array_numbers, n):
    if n > len(array_numbers):
      return []
    temp_sum = sum(array_numbers[:n])
    averages = [temp_sum / float(n)]
    for first_index, item in enumerate(array_numbers[n:]):
        temp_sum += item - array_numbers[first_index]
        averages.append(temp_sum / float(n))
    return averages
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.