it-swarm.com.ru

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

Есть ли в Python функция scipy, функция numpy или модуль, который вычисляет среднее значение для одномерного массива в конкретном окне?

132
Shejo284

Для короткого, быстрого решения, которое делает все это в одном цикле, без зависимостей, приведенный ниже код отлично работает.

mylist = [1, 2, 3, 4, 5, 6, 7]
N = 3
cumsum, moving_aves = [0], []

for i, x in enumerate(mylist, 1):
    cumsum.append(cumsum[i-1] + x)
    if i>=N:
        moving_ave = (cumsum[i] - cumsum[i-N])/N
        #can do stuff with moving_ave here
        moving_aves.append(moving_ave)
16
Aikude

UPD: более эффективные решения были предложены Alleo и jasaarim .


Вы можете использовать np.convolve для этого: 

np.convolve(x, np.ones((N,))/N, mode='valid')

Объяснение

Скользящее среднее - это случай математической операции свертка . Для скользящего среднего вы перемещаете окно вдоль ввода и вычисляете среднее значение содержимого окна. Для дискретных одномерных сигналов свертка - это то же самое, за исключением того, что вместо среднего значения вы вычисляете произвольную линейную комбинацию, то есть умножаете каждый элемент на соответствующий коэффициент и суммируете результаты. Эти коэффициенты, по одному для каждой позиции в окне, иногда называют сверткой ядро. Теперь среднее арифметическое значений N равно (x_1 + x_2 + ... + x_N) / N, поэтому соответствующее ядро ​​(1/N, 1/N, ..., 1/N), и это именно то, что мы получаем, используя np.ones((N,))/N.

Ребра

Аргумент modenp.convolve определяет, как обрабатывать края. Я выбрал режим valid, потому что я думаю, что именно так большинство людей ожидает, что сработает среднее значение, но у вас могут быть другие приоритеты. Вот график, который иллюстрирует разницу между режимами:

import numpy as np
import matplotlib.pyplot as plt
modes = ['full', 'same', 'valid']
for m in modes:
    plt.plot(np.convolve(np.ones((200,)), np.ones((50,))/50, mode=m));
plt.axis([-10, 251, -.1, 1.1]);
plt.legend(modes, loc='lower center');
plt.show()

Running mean convolve modes

172
lapis

Эффективное решение

Свертка намного лучше, чем простой подход, но (я думаю) она использует БПФ и, следовательно, довольно медленно. Однако, специально для вычисления бегущего среднего, следующий подход работает нормально

def running_mean(x, N):
    cumsum = numpy.cumsum(numpy.insert(x, 0, 0)) 
    return (cumsum[N:] - cumsum[:-N]) / float(N)

Код для проверки

In[3]: x = numpy.random.random(100000)
In[4]: N = 1000
In[5]: %timeit result1 = numpy.convolve(x, numpy.ones((N,))/N, mode='valid')
10 loops, best of 3: 41.4 ms per loop
In[6]: %timeit result2 = running_mean(x, N)
1000 loops, best of 3: 1.04 ms per loop

Обратите внимание, что numpy.allclose(result1, result2) - это True, два метода эквивалентны . Чем больше N, тем больше разница во времени.

110
Alleo

Update: В приведенном ниже примере показана старая функция pandas.rolling_mean, которая была удалена в последних версиях pandas. Современный эквивалент вызова функции ниже будет

In [8]: pd.Series(x).rolling(window=N).mean().iloc[N-1:].values
Out[8]: 
array([ 0.49815397,  0.49844183,  0.49840518, ...,  0.49488191,
        0.49456679,  0.49427121])

Панды больше подходит для этого, чем NumPy или SciPy. Его функция Rolling_mean делает работу удобно. Он также возвращает массив NumPy, когда входные данные являются массивом.

Сложно превзойти rolling_mean в производительности с любой собственной чистой реализацией Python. Вот пример производительности по сравнению с двумя из предложенных решений: 

In [1]: import numpy as np

In [2]: import pandas as pd

In [3]: def running_mean(x, N):
   ...:     cumsum = np.cumsum(np.insert(x, 0, 0)) 
   ...:     return (cumsum[N:] - cumsum[:-N]) / N
   ...:

In [4]: x = np.random.random(100000)

In [5]: N = 1000

In [6]: %timeit np.convolve(x, np.ones((N,))/N, mode='valid')
10 loops, best of 3: 172 ms per loop

In [7]: %timeit running_mean(x, N)
100 loops, best of 3: 6.72 ms per loop

In [8]: %timeit pd.rolling_mean(x, N)[N-1:]
100 loops, best of 3: 4.74 ms per loop

In [9]: np.allclose(pd.rolling_mean(x, N)[N-1:], running_mean(x, N))
Out[9]: True

Есть также хорошие варианты того, как работать со значениями Edge.

62
jasaarim

Вы можете рассчитать среднее значение с помощью:

import numpy as np

def runningMean(x, N):
    y = np.zeros((len(x),))
    for ctr in range(len(x)):
         y[ctr] = np.sum(x[ctr:(ctr+N)])
    return y/N

Но это медленно.

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

def runningMeanFast(x, N):
    return np.convolve(x, np.ones((N,))/N)[(N-1):]

На моей машине быстрая версия работает в 20-30 раз быстрее, в зависимости от длины входного вектора и размера окна усреднения.

Обратите внимание, что в convolve есть режим 'same', который, похоже, должен решать начальную временную проблему, но разделяет его между началом и концом.

48
mtrw

или модуль для Python, который рассчитывает

в моих тестах на Tradewave.net TA-lib всегда побеждает:

import talib as ta
import numpy as np
import pandas as pd
import scipy
from scipy import signal
import time as t

PAIR = info.primary_pair
PERIOD = 30

def initialize():
    storage.reset()
    storage.elapsed = storage.get('elapsed', [0,0,0,0,0,0])

def cumsum_sma(array, period):
    ret = np.cumsum(array, dtype=float)
    ret[period:] = ret[period:] - ret[:-period]
    return ret[period - 1:] / period

def pandas_sma(array, period):
    return pd.rolling_mean(array, period)

def api_sma(array, period):
    # this method is native to Tradewave and does NOT return an array
    return (data[PAIR].ma(PERIOD))

def talib_sma(array, period):
    return ta.MA(array, period)

def convolve_sma(array, period):
    return np.convolve(array, np.ones((period,))/period, mode='valid')

def fftconvolve_sma(array, period):    
    return scipy.signal.fftconvolve(
        array, np.ones((period,))/period, mode='valid')    

def tick():

    close = data[PAIR].warmup_period('close')

    t1 = t.time()
    sma_api = api_sma(close, PERIOD)
    t2 = t.time()
    sma_cumsum = cumsum_sma(close, PERIOD)
    t3 = t.time()
    sma_pandas = pandas_sma(close, PERIOD)
    t4 = t.time()
    sma_talib = talib_sma(close, PERIOD)
    t5 = t.time()
    sma_convolve = convolve_sma(close, PERIOD)
    t6 = t.time()
    sma_fftconvolve = fftconvolve_sma(close, PERIOD)
    t7 = t.time()

    storage.elapsed[-1] = storage.elapsed[-1] + t2-t1
    storage.elapsed[-2] = storage.elapsed[-2] + t3-t2
    storage.elapsed[-3] = storage.elapsed[-3] + t4-t3
    storage.elapsed[-4] = storage.elapsed[-4] + t5-t4
    storage.elapsed[-5] = storage.elapsed[-5] + t6-t5    
    storage.elapsed[-6] = storage.elapsed[-6] + t7-t6        

    plot('sma_api', sma_api)  
    plot('sma_cumsum', sma_cumsum[-5])
    plot('sma_pandas', sma_pandas[-10])
    plot('sma_talib', sma_talib[-15])
    plot('sma_convolve', sma_convolve[-20])    
    plot('sma_fftconvolve', sma_fftconvolve[-25])

def stop():

    log('ticks....: %s' % info.max_ticks)

    log('api......: %.5f' % storage.elapsed[-1])
    log('cumsum...: %.5f' % storage.elapsed[-2])
    log('pandas...: %.5f' % storage.elapsed[-3])
    log('talib....: %.5f' % storage.elapsed[-4])
    log('convolve.: %.5f' % storage.elapsed[-5])    
    log('fft......: %.5f' % storage.elapsed[-6])

результаты:

[2015-01-31 23:00:00] ticks....: 744
[2015-01-31 23:00:00] api......: 0.16445
[2015-01-31 23:00:00] cumsum...: 0.03189
[2015-01-31 23:00:00] pandas...: 0.03677
[2015-01-31 23:00:00] talib....: 0.00700  # <<< Winner!
[2015-01-31 23:00:00] convolve.: 0.04871
[2015-01-31 23:00:00] fft......: 0.22306

 enter image description here

19
litepresence

Готовое к использованию решение см. В разделе https://scipy-cookbook.readthedocs.io/items/SignalSmooth.html . Оно обеспечивает скользящее среднее с типом окна flat. Обратите внимание, что это немного сложнее, чем простой метод «сделай сам», так как он пытается решить проблемы в начале и конце данных, отражая их (что может или не может работать в вашем случае. ..).

Для начала вы можете попробовать:

a = np.random.random(100)
plt.plot(a)
b = smooth(a, window='flat')
plt.plot(b)
16
Hansemann

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

ПРИМЕЧАНИЕ: это было бы намного быстрее, если бы использовать пустой массив вместо списка, но я хотел устранить все зависимости. Также было бы возможно улучшить производительность путем многопоточного исполнения.

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

### Running mean/Moving average
def running_mean(l, N):
    sum = 0
    result = list( 0 for x in l)

    for i in range( 0, N ):
        sum = sum + l[i]
        result[i] = sum / (i+1)

    for i in range( N, len(l) ):
        sum = sum - l[i-N] + l[i]
        result[i] = sum / N

    return result
12
NeXuS

Если важно сохранить размеры входных данных (вместо ограничения выходных данных областью свертки 'valid'), вы можете использовать scipy.ndimage.filters.uniform_filter1d :

import numpy as np
from scipy.ndimage.filters import uniform_filter1d
N = 1000
x = np.random.random(100000)
y = uniform_filter1d(x, size=N)

y.shape == x.shape
>>> True

uniform_filter1d позволяет несколькими способами обрабатывать границу, где 'reflect' является значением по умолчанию, но в моем случае я предпочел 'nearest'.

Это также довольно быстро (почти в 50 раз быстрее, чем np.convolve):

%timeit y1 = np.convolve(x, np.ones((N,))/N, mode='same')
100 loops, best of 3: 9.28 ms per loop

%timeit y2 = uniform_filter1d(x, size=N)
10000 loops, best of 3: 191 µs per loop
10
moi

Я еще не проверил, насколько это быстро, но вы можете попробовать:

from collections import deque

cache = deque() # keep track of seen values
n = 10          # window size
A = xrange(100) # some dummy iterable
cum_sum = 0     # initialize cumulative sum

for t, val in enumerate(A, 1):
    cache.append(val)
    cum_sum += val
    if t < n:
        avg = cum_sum / float(t)
    else:                           # if window is saturated,
        cum_sum -= cache.popleft()  # subtract oldest value
        avg = cum_sum / float(n)
5
Kris

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

Метод представляет собой простое матричное умножение с нормализованным ядром Гаусса.

def running_mean(y_in, x_in, N_out=101, sigma=1):
    '''
    Returns running mean as a Bell-curve weighted average at evenly spaced
    points. Does NOT wrap signal around, or pad with zeros.

    Arguments:
    y_in -- y values, the values to be smoothed and re-sampled
    x_in -- x values for array

    Keyword arguments:
    N_out -- NoOf elements in resampled array.
    sigma -- 'Width' of Bell-curve in units of param x .
    '''
    N_in = size(y_in)

    # Gaussian kernel
    x_out = np.linspace(np.min(x_in), np.max(x_in), N_out)
    x_in_mesh, x_out_mesh = np.meshgrid(x_in, x_out)
    gauss_kernel = np.exp(-np.square(x_in_mesh - x_out_mesh) / (2 * sigma**2))
    # Normalize kernel, such that the sum is one along axis 1
    normalization = np.tile(np.reshape(sum(gauss_kernel, axis=1), (N_out, 1)), (1, N_in))
    gauss_kernel_normalized = gauss_kernel / normalization
    # Perform running average as a linear operation
    y_out = gauss_kernel_normalized @ y_in

    return y_out, x_out

Простое использование синусоидального сигнала с добавлением нормального распределенного шума:  enter image description here

4
Clausen

Другой подход к поиску скользящей средней без с использованием numpy, panda

import itertools
sample = [2, 6, 10, 8, 11, 10]
list(itertools.starmap(lambda a,b: b/a, 
               enumerate(itertools.accumulate(sample), 1)))

напечатает [2.0, 4.0, 6.0, 6.5, 7.4, 7.833333333333333]

3
DmitrySemenov

Вместо того, чтобы болтать или скучать, я бы порекомендовал пандам сделать это быстрее:

df['data'].rolling(3).mean()

Для этого берется скользящее среднее (MA) из 3 периодов столбца «данные». Вы также можете рассчитать смещенные версии, например, ту, которая исключает текущую ячейку (смещенную на одну назад), можно легко вычислить как:

df['data'].shift(periods=1).rolling(3).mean()
3
Gursel Karacor

Этот вопрос теперь даже старше, чем когда NeXuS писал об этом в прошлом месяце, НО мне нравится, как его код обрабатывает случаи Edge. Однако, поскольку это «простая скользящая средняя», его результаты отстают от данных, к которым они применяются. Я думал, что работа с Edge-случаями более удовлетворительная, чем в режимах NumPy valid, same и full, может быть достигнута путем применения аналогичного подхода к методу, основанному на convolution().

Мой вклад использует центральное скользящее среднее, чтобы привести результаты в соответствие с их данными. Если для полноразмерного окна доступно слишком мало точек, средние значения вычисляются из последовательно меньших окон по краям массива. [На самом деле, из последовательно увеличивающихся окон, но это деталь реализации.]

import numpy as np

def running_mean(l, N):
    # Also works for the(strictly invalid) cases when N is even.
    if (N//2)*2 == N:
        N = N - 1
    front = np.zeros(N//2)
    back = np.zeros(N//2)

    for i in range(1, (N//2)*2, 2):
        front[i//2] = np.convolve(l[:i], np.ones((i,))/i, mode = 'valid')
    for i in range(1, (N//2)*2, 2):
        back[i//2] = np.convolve(l[-i:], np.ones((i,))/i, mode = 'valid')
    return np.concatenate([front, np.convolve(l, np.ones((N,))/N, mode = 'valid'), back[::-1]])

Он относительно медленный, потому что он использует convolve(), и, скорее всего, истинный Pythonista может его украсить, но я считаю, что идея верна.

3
marisano

Этот ответ содержит решения, использующие Python standard library для трех различных сценариев.


Скользящее среднее с itertools.accumulate

Это решение Python 3.2+ с эффективным использованием памяти, вычисляющее скользящее среднее по итерируемым значениям с использованием itertools.accumulate.

>>> from itertools import accumulate
>>> values = range(100)

Обратите внимание, что values может быть любым итеративным, включая генераторы или любой другой объект, который генерирует значения на лету.

Во-первых, лениво построить совокупную сумму значений.

>>> cumu_sum = accumulate(value_stream)

Затем enumerate накопленную сумму (начиная с 1) и создайте генератор, который выдает долю накопленных значений и текущий индекс перечисления.

>>> rolling_avg = (accu/i for i, accu in enumerate(cumu_sum, 1))

Вы можете вызвать means = list(rolling_avg), если вам нужны все значения в памяти одновременно, или вызывать next постепенно.
(Конечно, вы также можете перебирать rolling_avg с помощью цикла for, который будет неявно вызывать next.)

>>> next(rolling_avg) # 0/1
>>> 0.0
>>> next(rolling_avg) # (0 + 1)/2
>>> 0.5
>>> next(rolling_avg) # (0 + 1 + 2)/3
>>> 1.0

Это решение можно записать в виде функции следующим образом.

from itertools import accumulate

def rolling_avg(iterable):
    cumu_sum = accumulate(iterable)
    yield from (accu/i for i, accu in enumerate(cumu_sum, 1))

A сопрограмма в которую вы можете отправлять значения в любое время

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

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

def rolling_avg_coro():
    i = 0
    total = 0.0
    avg = None

    while True:
        next_value = yield avg
        i += 1
        total += next_value
        avg = total/i

Сопрограмма работает так:

>>> averager = rolling_avg_coro() # instantiate coroutine
>>> next(averager) # get coroutine going (this is called priming)
>>>
>>> averager.send(5) # 5/1
>>> 5.0
>>> averager.send(3) # (5 + 3)/2
>>> 4.0
>>> print('doing something else...')
doing something else...
>>> averager.send(13) # (5 + 3 + 13)/3
>>> 7.0

Вычисление среднего по скользящему окну размером N

Эта функция-генератор принимает итеративный размер окна N и выдает среднее значение по текущим значениям внутри окна. Он использует deque , который представляет собой структуру данных, похожую на список, но оптимизирован для быстрых модификаций (pop, append) в обеих конечных точках.

from collections import deque
from itertools import islice

def sliding_avg(iterable, N):        
    it = iter(iterable)
    window = deque(islice(it, N))        
    num_vals = len(window)

    if num_vals < N:
        msg = 'window size {} exceeds total number of values {}'
        raise ValueError(msg.format(N, num_vals))

    N = float(N) # force floating point division if using Python 2
    s = sum(window)

    while True:
        yield s/N
        try:
            nxt = next(it)
        except StopIteration:
            break
        s = s - window.popleft() + nxt
        window.append(nxt)

Вот функция в действии:

>>> values = range(100)
>>> N = 5
>>> window_avg = sliding_avg(values, N)
>>> 
>>> next(window_avg) # (0 + 1 + 2 + 3 + 4)/5
>>> 2.0
>>> next(window_avg) # (1 + 2 + 3 + 4 + 5)/5
>>> 3.0
>>> next(window_avg) # (2 + 3 + 4 + 5 + 6)/5
>>> 4.0
2
timgeb

Есть много ответов выше о вычислении среднего значения. Мой ответ добавляет две дополнительные функции:

  1. игнорирует значения нан 
  2. вычисляет среднее значение для N соседних значений, НЕ включая значение самого интереса

Эта вторая особенность особенно полезна для определения того, какие значения отличаются от общей тенденции на определенную величину.

Я использую numpy.cumsum, так как это наиболее эффективный по времени метод ( см. Ответ Аллео выше ). 

N=10 # number of points to test on each side of point of interest, best if even
padded_x = np.insert(np.insert( np.insert(x, len(x), np.empty(int(N/2))*np.nan), 0, np.empty(int(N/2))*np.nan ),0,0)
n_nan = np.cumsum(np.isnan(padded_x))
cumsum = np.nancumsum(padded_x) 
window_sum = cumsum[N+1:] - cumsum[:-(N+1)] - x # subtract value of interest from sum of all values within window
window_n_nan = n_nan[N+1:] - n_nan[:-(N+1)] - np.isnan(x)
window_n_values = (N - window_n_nan)
movavg = (window_sum) / (window_n_values)

Этот код работает только для четных Ns. Его можно отрегулировать для нечетных чисел, изменив np.insert из padded_x и n_nan.

Пример вывода (raw в черном, movavg в синем):  raw data (black) and moving average (blue) of 10 points around each value, not including that value. nan values are ignored.

Этот код может быть легко адаптирован для удаления всех значений скользящих средних, рассчитанных из меньшего, чем значения отсечки = 3 не-нанона.

window_n_values = (N - window_n_nan).astype(float) # dtype must be float to set some values to nan
cutoff = 3
window_n_values[window_n_values<cutoff] = np.nan
movavg = (window_sum) / (window_n_values)

 raw data (black) and moving average (blue) while ignoring any window with fewer than 3 non-nan values

2
gtcoder

Использовать только библиотеку Python Stadnard (эффективное использование памяти)

Просто дайте другую версию использования только стандартной библиотеки deque. Для меня довольно удивительно, что большинство ответов используют pandas или numpy.

def moving_average(iterable, n=3):
    d = deque(maxlen=n)
    for i in iterable:
        d.append(i)
        if len(d) == n:
            yield sum(d)/n

r = moving_average([40, 30, 50, 46, 39, 44])
assert list(r) == [40.0, 42.0, 45.0, 43.0]

На самом деле я нашел другую реализацию в документации по Python

def moving_average(iterable, n=3):
    # moving_average([40, 30, 50, 46, 39, 44]) --> 40.0 42.0 45.0 43.0
    # http://en.wikipedia.org/wiki/Moving_average
    it = iter(iterable)
    d = deque(itertools.islice(it, n-1))
    d.appendleft(0)
    s = sum(d)
    for elem in it:
        s += elem - d.popleft()
        d.append(elem)
        yield s / n

Однако реализация кажется мне более сложной, чем должна быть. Но по какой-то причине это должно быть в стандартных документах на Python, может кто-нибудь прокомментировать реализацию моей и стандартной документации?

1
MaThMaX

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

См основной образец ниже:

import numpy as np
import bottleneck as bn

a = np.random.randint(4, 1000, size=100)
mm = bn.move_mean(a, window=5, min_count=1)
  • «мм» - это скользящее среднее для «а». 

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

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

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

1
Anthony Anyanwu

Есть комментарий от mab похоронен в одном из ответов выше, который имеет этот метод. bottleneck имеет move_mean, который представляет собой простое скользящее среднее:

import numpy as np
import bottleneck as bn

a = np.arange(10) + np.random.random(10)

mva = bn.move_mean(a, window=2, min_count=1)

min_count - это удобный параметр, который в основном поднимает скользящее среднее до этой точки в вашем массиве. Если вы не установите min_count, он будет равен window, и все, вплоть до window баллов, будет nan.

1
wordsforthewise

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

import numpy as np
dataset = np.asarray([1, 2, 3, 4, 5, 6, 7])
ma = list()
window = 3
for t in range(0, len(dataset)):
    if t+window <= len(dataset):
        indices = range(t, t+window)
        ma.append(np.average(np.take(dataset, indices)))
else:
    ma = np.asarray(ma)
1
Ayberk Yavuz

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

Поэтому, если вы хотите сохранить список значений, которые вы получаете откуда-то (сайт, измерительное устройство и т.д.), И среднее значение последних n обновленных значений, вы можете использовать приведенный ниже код, который минимизирует усилия по добавлению новые элементы:

class Running_Average(object):
    def __init__(self, buffer_size=10):
        """
        Create a new Running_Average object.

        This object allows the efficient calculation of the average of the last
        `buffer_size` numbers added to it.

        Examples
        --------
        >>> a = Running_Average(2)
        >>> a.add(1)
        >>> a.get()
        1.0
        >>> a.add(1)  # there are two 1 in buffer
        >>> a.get()
        1.0
        >>> a.add(2)  # there's a 1 and a 2 in the buffer
        >>> a.get()
        1.5
        >>> a.add(2)
        >>> a.get()  # now there's only two 2 in the buffer
        2.0
        """
        self._buffer_size = int(buffer_size)  # make sure it's an int
        self.reset()

    def add(self, new):
        """
        Add a new number to the buffer, or replaces the oldest one there.
        """
        new = float(new)  # make sure it's a float
        n = len(self._buffer)
        if n < self.buffer_size:  # still have to had numbers to the buffer.
            self._buffer.append(new)
            if self._average != self._average:  # ~ if isNaN().
                self._average = new  # no previous numbers, so it's new.
            else:
                self._average *= n  # so it's only the sum of numbers.
                self._average += new  # add new number.
                self._average /= (n+1)  # divide by new number of numbers.
        else:  # buffer full, replace oldest value.
            old = self._buffer[self._index]  # the previous oldest number.
            self._buffer[self._index] = new  # replace with new one.
            self._index += 1  # update the index and make sure it's...
            self._index %= self.buffer_size  # ... smaller than buffer_size.
            self._average -= old/self.buffer_size  # remove old one...
            self._average += new/self.buffer_size  # ...and add new one...
            # ... weighted by the number of elements.

    def __call__(self):
        """
        Return the moving average value, for the lazy ones who don't want
        to write .get .
        """
        return self._average

    def get(self):
        """
        Return the moving average value.
        """
        return self()

    def reset(self):
        """
        Reset the moving average.

        If for some reason you don't want to just create a new one.
        """
        self._buffer = []  # could use np.empty(self.buffer_size)...
        self._index = 0  # and use this to keep track of how many numbers.
        self._average = float('nan')  # could use np.NaN .

    def get_buffer_size(self):
        """
        Return current buffer_size.
        """
        return self._buffer_size

    def set_buffer_size(self, buffer_size):
        """
        >>> a = Running_Average(10)
        >>> for i in range(15):
        ...     a.add(i)
        ...
        >>> a()
        9.5
        >>> a._buffer  # should not access this!!
        [10.0, 11.0, 12.0, 13.0, 14.0, 5.0, 6.0, 7.0, 8.0, 9.0]

        Decreasing buffer size:
        >>> a.buffer_size = 6
        >>> a._buffer  # should not access this!!
        [9.0, 10.0, 11.0, 12.0, 13.0, 14.0]
        >>> a.buffer_size = 2
        >>> a._buffer
        [13.0, 14.0]

        Increasing buffer size:
        >>> a.buffer_size = 5
        Warning: no older data available!
        >>> a._buffer
        [13.0, 14.0]

        Keeping buffer size:
        >>> a = Running_Average(10)
        >>> for i in range(15):
        ...     a.add(i)
        ...
        >>> a()
        9.5
        >>> a._buffer  # should not access this!!
        [10.0, 11.0, 12.0, 13.0, 14.0, 5.0, 6.0, 7.0, 8.0, 9.0]
        >>> a.buffer_size = 10  # reorders buffer!
        >>> a._buffer
        [5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0]
        """
        buffer_size = int(buffer_size)
        # order the buffer so index is zero again:
        new_buffer = self._buffer[self._index:]
        new_buffer.extend(self._buffer[:self._index])
        self._index = 0
        if self._buffer_size < buffer_size:
            print('Warning: no older data available!')  # should use Warnings!
        else:
            diff = self._buffer_size - buffer_size
            print(diff)
            new_buffer = new_buffer[diff:]
        self._buffer_size = buffer_size
        self._buffer = new_buffer

    buffer_size = property(get_buffer_size, set_buffer_size)

И вы можете проверить это, например:

def graph_test(N=200):
    import matplotlib.pyplot as plt
    values = list(range(N))
    values_average_calculator = Running_Average(N/2)
    values_averages = []
    for value in values:
        values_average_calculator.add(value)
        values_averages.append(values_average_calculator())
    fig, ax = plt.subplots(1, 1)
    ax.plot(values, label='values')
    ax.plot(values_averages, label='averages')
    ax.grid()
    ax.set_xlim(0, N)
    ax.set_ylim(0, N)
    fig.show()

Который дает:

 Values and their average as a function of values #

1
berna1111

В образовательных целях позвольте мне добавить еще два решения Numpy (которые работают медленнее, чем решение cumsum):

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

def ra_strides(arr, window):
    ''' Running average using as_strided'''
    n = arr.shape[0] - window + 1
    arr_strided = as_strided(arr, shape=[n, window], strides=2*arr.strides)
    return arr_strided.mean(axis=1)

def ra_add(arr, window):
    ''' Running average using add.reduceat'''
    n = arr.shape[0] - window + 1
    indices = np.array([0, window]*n) + np.repeat(np.arange(n), 2)
    arr = np.append(arr, 0)
    return np.add.reduceat(arr, indices )[::2]/window

Используемые функции: as_strided , add.reduceat

0
AndyK

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

lfilter(np.ones(N)/N, [1], a)[N:]

И с примененным треугольным окном:

lfilter(np.ones(N)*scipy.signal.triang(N)/N, [1], a)[N:]
0
mac13k

Другое решение, использующее стандартную библиотеку и deque:

from collections import deque
import itertools

def moving_average(iterable, n=3):
    # http://en.wikipedia.org/wiki/Moving_average
    it = iter(iterable) 
    # create an iterable object from input argument
    d = deque(itertools.islice(it, n-1))  
    # create deque object by slicing iterable
    d.appendleft(0)
    s = sum(d)
    for elem in it:
        s += elem - d.popleft()
        d.append(elem)
        yield s / n

# example on how to use it
for i in  moving_average([40, 30, 50, 46, 39, 44]):
    print(i)

# 40.0
# 42.0
# 45.0
# 43.0
0
Vlad Bezden