Ковзна середня або біжна середня


195

Чи є функція SciPy або функція або модуль NumPy для Python, який обчислює середнє значення запуску 1D масиву, що задається певним вікном?

Відповіді:


24

Для короткого швидкого рішення, яке робить все в один цикл, без залежностей, код нижче працює чудово.

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)

46
Швидкий ?! Це рішення на порядок повільніше, ніж рішення з Numpy.
Барт

3
Хоча це нативне рішення класне, ОП попросило функцію нуме / шипіння - імовірно, вони будуть значно швидшими.
Деміс

255

UPD: більш ефективні рішення були запропоновані Alleo та jasaarim .


Ви можете використовувати np.convolveдля цього:

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

Пояснення

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

Краї

modeАргумент np.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()

Запуск середніх режимів згортання


5
Мені подобається це рішення, оскільки воно чисте (один рядок) і відносно ефективне (робота, виконана всередині нумерованої). Але «Ефективне рішення» Аллео numpy.cumsumмає кращі складності.
Ульріх Штерн

2
@denfromufa, я вважаю, що документація охоплює впровадження досить добре, і вона також посилається на Вікіпедію, яка пояснює математику. Беручи до уваги питання, чи вважаєте ви, що ця відповідь потребує копіювання?
lapis

@lapis використання convolve для ковзної середньої є досить незвичним і неочевидним. Ось найкраще наочне пояснення, яке я знайшов: matlabtricks.com/post-11/moving-average-by-convolution
denfromufa

Для побудови графіків та пов'язаних із ними завдань було б корисно заповнити значенням None. Моя (не така красива, але коротка) пропозиція: `` `def move_average (x, N, fill = True): return np.concatenate ([x for x in [[None] * (N // 2 + N% 2) * fill, np.convolve (x, np.ones ((N,)) / N, mode = 'дійсний'), [None] * (N // 2) * fill,] if len (x)]) ` `` Код виглядає так некрасиво у коментарях ТАК xD Я не хотів додати ще однієї відповіді, оскільки їх було так багато, але ви можете просто скопіювати та вставити його у свій IDE.
Chaoste

146

Ефективне рішення

Згортання набагато краще, ніж прямолінійний підхід, але (я думаю) він використовує FFT і, таким чином, досить повільний. Однак спеціально для обчислення середнього бігу наступний підхід працює чудово

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, тим більша різниця у часі.

попередження: хоча швидше закінчується обробка, збільшується помилка з плаваючою комою, що може призвести до неправильності / неправильності / неприйнятності результатів

коментарі вказали на цю помилку з плаваючою комою тут, але я роблю це більш очевидним тут у відповіді. .

# demonstrate loss of precision with only 100,000 points
np.random.seed(42)
x = np.random.randn(100000)+1e6
y1 = running_mean_convolve(x, 10)
y2 = running_mean_cumsum(x, 10)
assert np.allclose(y1, y2, rtol=1e-12, atol=0)
  • чим більше балів ви накопичуєте, тим більша помилка з плаваючою точкою (тому 1e5 балів помітно, 1e6 балів є більш значущими, більше 1e6, і ви, можливо, захочете скинути акумулятори)
  • ви можете обдурити, використовуючи, np.longdoubleале помилка з плаваючою комою все одно отримає істотну для відносно великої кількості очок (приблизно> 1e5, але залежить від ваших даних)
  • ви можете побудувати помилку і побачити її порівняно швидко
  • рішення згортки повільніше, але не має цієї плаваючої точки втрати точності
  • рішення uniform_filter1d швидше, ніж це рішення "Cumsum" І не втрачає точності

3
Приємне рішення! Моя голочка - numpy.convolveце O (mn); його документи згадують, що scipy.signal.fftconvolveвикористовує FFT.
Ульріх Штерн

3
Цей метод не стосується ребер масиву, чи не так?
JoVe

6
Приємне рішення, але зауважте, що він може зазнати чисельних помилок для великих масивів, оскільки до кінця масиву ви можете відняти два великі числа, щоб отримати невеликий результат.
Bas Swinckels

1
При цьому використовується ціле ділення замість поплавкового поділу: running_mean([1,2,3], 2)дає array([1, 2]). Заміна xпо [float(value) for value in x]робить трюк.
ChrisW

4
Чисельна стабільність цього рішення може стати проблемою, якщо вона xмістить поплавці. Приклад: running_mean(np.arange(int(1e7))[::-1] + 0.2, 1)[-1] - 0.2повертається, 0.003125поки очікуєш 0.0. Більше інформації: en.wikipedia.org/wiki/Loss_of_significance
Мілан

80

Оновлення: У наведеному нижче прикладі показана стара pandas.rolling_meanфункція, яка була видалена в останніх версіях панд. Сучасний еквівалент функціонального виклику нижче

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

Також є приємні варіанти, як поводитися з крайовими значеннями.


6
Pandas rolling_mean - хороший інструмент для роботи, але застарілий для ndarrays. У майбутніх випусках Pandas він буде функціонувати лише на серіях Pandas. Куди ми звернемось тепер до даних масиву, що не належить Pandas?
Майк

5
@Mike rolling_mean () застарілий, але тепер ви можете використовувати прокат і означати окремо: df.rolling(windowsize).mean()тепер працює замість цього (дуже швидко я можу додати). за 6000 рядів рядів %timeit test1.rolling(20).mean()повернуто 1000 циклів, найкраще 3: 1,16 мс за цикл
Vlox

5
@Vlox df.rolling()працює досить добре, проблема полягає в тому, що навіть ця форма не підтримуватиме ndarrays у майбутньому. Для його використання нам доведеться спочатку завантажити наші дані в Панель даних Панди. Я хотів би, щоб ця функція була додана до numpyабо scipy.signal.
Майк

1
@Mike повністю згоден. Я намагаюся, зокрема, відповідати швидкості pandas .ewm (). Mean () для власних масивів (замість того, щоб спочатку завантажувати їх у df). Я маю на увазі, це здорово, що це швидко, але просто відчуваєш себе трохи незграбно переміщаючись у фрейми даних і виходячи з них занадто часто.
Vlox

6
%timeit bottleneck.move_mean(x, N)в 3 - 15 разів швидше, ніж методи сперми та панди на моєму ПК. Подивіться на їх еталоном РЕПО в README .
маб

50

Ви можете обчислити середнє значення ходу за допомогою:

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 включає в себе згортку функцію , яку ми можемо використовувати , щоб прискорити процес . Середнє біг еквівалентний згортці xз вектором, який Nдовгий, з усіма членами, рівними 1/N. Нумерована реалізація convolve включає в себе початковий перехідний процес, тому вам доведеться видалити перші точки N-1:

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

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

Зауважте, що convolve включає в себе 'same'режим, який, здається, повинен вирішувати стартову проблему, що переходить, але розбиває її між початком і кінцем.


Зауважте, що видалення перших точок N-1 все ще залишає граничний ефект в останніх точках. Найпростіший спосіб вирішити проблему - це використання, mode='valid'в convolveякому не потрібно жодної післяобробки.
lapis

1
@Psycho - mode='valid'видаляє перехідне з обох кінців, правда? Якщо len(x)=10і N=4, для середнього запуску, я хотів би 10 результатів, але validповертається 7.
mtrw

1
Це видаляє перехідне від кінця, а початок не має. Ну, я думаю, це питання пріоритетів, мені не потрібно однакової кількості результатів за рахунок нахилу до нуля, якого немає в даних. BTW, ось команда показати різницю між режимами: modes = ('full', 'same', 'valid'); [plot(convolve(ones((200,)), ones((50,))/50, mode=m)) for m in modes]; axis([-10, 251, -.1, 1.1]); legend(modes, loc='lower center')(з імпортованим pyplot та numpy).
lapis

runningMeanЧи є у мене побічний ефект усереднення нулів, коли ви виходите з масиву з x[ctr:(ctr+N)]правою стороною масиву.
mrgloom

runningMeanFastтакож це питання ефекту кордону.
mrgloom

22

або модуль для 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

введіть тут опис зображення


NameError: name 'info' is not defined. Я отримую цю помилку, сер.
Пані Резванул Хаке

1
Схоже, що часовий ряд зміщується після згладжування, це бажаний ефект?
мрглоом

@mrgloom так, для візуалізації; в іншому випадку вони з'являться як один рядок на діаграмі; Пані Резванул Хаке, ви можете видалити всі посилання на PAIR та інформацію; це були внутрішні пісочні методи для теперішнього неіснуючого tradewave.net
litepresence

21

Про готове до використання рішення див. 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)

1
Цей метод спирається на numpy.convolveрізницю лише в зміні послідовності.
Аллео

10
Мене завжди дратує функція обробки сигналів, яка повертає вихідні сигнали різної форми, ніж вхідні сигнали, коли і входи, і виходи мають однаковий характер (наприклад, обидва тимчасові сигнали). Це розбиває відповідність із пов'язаною незалежною змінною (наприклад, часом, частотою), що робить побудову чи порівняння не прямим питанням ... у будь-якому випадку, якщо ви поділитесь почуттям, ви можете змінити останні рядки запропонованої функції як y = np .convolve (w / w.sum (), s, mode = 'той же'); повернути y [window_len-1 :-( window_len-1)]
Christian O'Reilly

@ ChristianO'Reilly, ви повинні опублікувати це як окрему відповідь - саме це я шукав, оскільки у мене справді є два інших масиви, які повинні відповідати довжині згладжених даних, для складання графіків тощо. Я б хотів знати як саме ви це зробили - wрозмір вікна та sдані?
Деміс

@Demis Радий, що коментар допоміг. Більше інформації про функцію numvol convolve тут docs.scipy.org/doc/numpy-1.15.0/reference/generated/… Функція згортання ( en.wikipedia.org/wiki/Convolution ) поєднує два сигнали один з одним. У цьому випадку він поєднує ваш сигнал (и) з нормалізованим (тобто унітарною областю) вікном (w / w.sum ()).
Крістіан О'Рейлі

21

Ви можете використовувати 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)

uniform_filter1d:

  • дає вихід з такою ж нумерованою формою (тобто кількістю очок)
  • дозволяє декілька способів обробляти кордон там, де 'reflect'за замовчуванням, але в моєму випадку я скоріше хотів'nearest'

Це також досить швидко (майже в 50 разів швидше np.convolveі в 2-5 разів швидше, ніж підхід, який наводиться вище, наведений вище ):

%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

ось 3 функції, які дозволяють порівнювати помилки / швидкість різних реалізацій:

from __future__ import division
import numpy as np
import scipy.ndimage.filters as ndif
def running_mean_convolve(x, N):
    return np.convolve(x, np.ones(N) / float(N), 'valid')
def running_mean_cumsum(x, N):
    cumsum = np.cumsum(np.insert(x, 0, 0))
    return (cumsum[N:] - cumsum[:-N]) / float(N)
def running_mean_uniform_filter1d(x, N):
    return ndif.uniform_filter1d(x, N, mode='constant', origin=-(N//2))[:-(N-1)]

1
Це єдина відповідь, яка, мабуть, враховує прикордонні питання (досить важлива, особливо при плануванні). Дякую!
Габріель

1
я профільовані uniform_filter1d, np.convolveз прямокутником, а np.cumsumпотім np.subtract. мої результати: (1.) convolve є найповільнішим. (2.) закінчення / віднімання швидше приблизно на 20-30 разів. (3.) uniform_filter1d приблизно в 2-3 рази швидше, ніж закінчення / віднімання. переможець, безумовно, є uniform_filter1d.
Тревор Бойд Сміт

використовуючи uniform_filter1dце швидше , ніж cumsumрозчин (приблизно 2-5x). і uniform_filter1d не отримує масивної помилки з плаваючою комою, як цеcumsum робить рішення.
Тревор Бойд Сміт

15

Я знаю, що це старе питання, але ось рішення, яке не використовує зайвих структур даних або бібліотек. Він лінійний за кількістю елементів вхідного списку, і я не можу придумати іншого способу зробити його більш ефективним (насправді, якщо хтось знає про кращий спосіб розподілу результату, будь ласка, дайте мені знати).

ПРИМІТКА. Це було б набагато швидше, використовуючи масив numpy замість списку, але я хотів усунути всі залежності. Також можна було б покращити продуктивність за допомогою багатопотокового виконання

Функція передбачає, що список введення є одномірним, тому будьте уважні.

### 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

Приклад

Припустимо, що у нас є список, data = [ 1, 2, 3, 4, 5, 6 ]за яким ми хочемо обчислити середнє значення прокрутки з періодом 3, і що ви також хочете, щоб список вихідних даних був однакового розміру вхідного (це найчастіше так).

Перший елемент має індекс 0, тому середнє кочення повинне обчислюватися на елементах індексу -2, -1 та 0. Очевидно, що у нас немає даних [-2] та даних [-1] (якщо ви не хочете використовувати спеціальні граничні умови), тому ми припускаємо, що цих елементів дорівнює 0. Це еквівалентно нульовому списку списку, за винятком того, що ми насправді його не додаємо, просто слідкуйте за індексами, які потребують заміщення (від 0 до N-1).

Отже, для перших N елементів ми просто продовжуємо додавати елементи в акумулятор.

result[0] = (0 + 0 + 1) / 3  = 0.333    ==   (sum + 1) / 3
result[1] = (0 + 1 + 2) / 3  = 1        ==   (sum + 2) / 3
result[2] = (1 + 2 + 3) / 3  = 2        ==   (sum + 3) / 3

Від елементів N + 1 вперед просто накопичення не працює. ми очікуємо, result[3] = (2 + 3 + 4)/3 = 3але це відрізняється від (sum + 4)/3 = 3.333.

Спосіб обчислити правильне значення відняти data[0] = 1з sum+4, таким чином даючи sum + 4 - 1 = 9.

Це відбувається тому, що в даний час sum = data[0] + data[1] + data[2], але це також справедливо для кожного, i >= Nоскільки перед відніманням sumє data[i-N] + ... + data[i-2] + data[i-1].


12

Я вважаю, що це можна елегантно вирішити за допомогою вузького місця

Дивіться базовий зразок нижче:

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)
  • "mm" - це рухоме значення для "a".

  • "window" - це максимальна кількість записів, які слід врахувати для ковзаючого середнього.

  • "min_count" - це мінімальна кількість записів, яку слід врахувати для ковзаючого середнього (наприклад, для перших кількох елементів або якщо масив має нан-значення).

Гарна частина полягає в тому, що Bottleneck допомагає боротися з значеннями нана, і це також дуже ефективно.


Ця лайка дійсно швидка. Функція чистого ковзного середнього рівня Python повільна. Bootleneck - це бібліотека PyData, яка, на мою думку, є стабільною і може отримати постійну підтримку з боку спільноти Python, так чому б не використати її?
GoingMyWay

6

Я ще не перевірив, наскільки це швидко, але ви можете спробувати:

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)

1
Це те, що я збирався зробити. Хтось може, будь ласка, критикувати, чому це поганий шлях?
стаггарт

1
Це просте рішення python для мене добре працювало, не вимагаючи нумету. Я в кінцевому підсумку перекинув його в клас для повторного використання.
Меттью Цчігг

6

Ця відповідь містить рішення, що використовують стандартну бібліотеку Python для трьох різних сценаріїв.


Середній показник с 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))
    

Сопрограммний , до якого ви можете відправити значення в будь-який час

Ця коренева програма споживає цінність, яку ви надсилаєте до неї, і зберігає середнє середнє значення до цих пір.

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

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 та дає середнє значення за поточні значення всередині вікна. Він використовує a 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

5

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

Метод - це просте множення матриць з нормалізованим ядром Гаусса.

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

Просте використання на синусоїдальному сигналі з додаванням нормального розподіленого шуму: введіть тут опис зображення


Це не працює для мене (python 3.6). 1 Там немає функції з ім'ям sum, використовуючи np.sumзамість 2 The @оператора (поняття не маю , що це таке) видає повідомлення про помилку. Я можу поглянути на це пізніше, але мені зараз бракує часу
Бастіан

Це @оператор множення матриць, який реалізує np.matmul . Перевірте, чи є ваш y_inмасив нумером, це може бути проблемою.
xyzzyqed

5

Замість нумети чи схипі я рекомендую пандам зробити це швидше:

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

Це займає ковзну середню (MA) 3 періоди стовпця "дані". Ви також можете обчислити зміщені версії, наприклад, ту, що виключає поточну комірку (зміщену одну назад), можна легко обчислити як:

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

Чим це відрізняється від рішення, запропонованого у 2016 році ?
Містер T

2
Рішення, запропоноване в 2016 році, використовує, pandas.rolling_meanа мінне pandas.DataFrame.rolling. Ви також можете легко розрахувати переміщення min(), max(), sum()тощо, а також за mean()допомогою цього методу.
Гурсель Каракор

У першому вам потрібно використовувати інший метод, наприклад, pandas.rolling_min, pandas.rolling_maxтощо. Вони схожі, але різні.
Гурсель Каракор

4

Є коментар МАБ похований в одній з відповідей, над якою є цей метод. 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.


3

Інший підхід до пошуку ковзної середньої без використання 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]


itertools.accumulate не існує в python 2.7, але є у python 3.4
grayaii

3

Це питання зараз навіть старше, ніж коли NeXuS писав про це минулого місяця, Але мені подобається, як його код стосується кращих справ. Однак, оскільки це "проста ковзаюча середня", її результати відстають від даних, до яких вони застосовуються. Я думав , що справа з крайніми випадками більш задовольняє чином , ніж режими 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

Вище є багато відповідей щодо обчислення середнього бігу. Моя відповідь додає дві додаткові функції:

  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.

Приклад виводу (сирий чорний колір, movavg синім кольором): необроблені дані (чорні) і ковзаюча середня (синя) 10 балів навколо кожного значення, не враховуючи цього значення.  значення нанів ігноруються.

Цей код може бути легко адаптований для видалення всіх ковзних середніх значень, обчислених від менших, ніж відріз = 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)

необроблені дані (чорні) та ковзаючі середні (сині), ігноруючи будь-яке вікно з меншими ніж 3 ненові значення


2

Використовувати лише стандартну бібліотеку Python (ефективність пам'яті)

Просто дайте іншу версію використання лише стандартної бібліотеки 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 чомусь, може хтось коментує реалізацію шахти та стандартного документа?


2
Одна велика різниця в тому, що ви продовжуєте підбивати підсумки членів вікна кожну ітерацію, і вони ефективно оновлюють суму (видаліть одного члена та додайте іншого). з точки зору складності ви робите O(n*d) розрахунки ( dякщо розмір вікна, nрозмір ітерабельного), і вони роблятьO(n)
Іфта

@Iftah, приємно, дякую за пояснення, ви праві.
MaThMaX

2

За допомогою змінних @ Aikude я написав однолінійку.

import numpy as np

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

mean = [np.mean(mylist[x:x+N]) for x in range(len(mylist)-N+1)]
print(mean)

>>> [2.0, 3.0, 4.0, 5.0, 6.0]

1

Хоча тут є рішення цього питання, будь ласка, погляньте на моє рішення. Це дуже просто і добре працює.

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

Читаючи інші відповіді, я не думаю, що це питання, про яке я задався, але я потрапив сюди з необхідністю зберігати середнє значення списку значень, що збільшувався в розмірах.

Отже, якщо ви хочете зберегти список значень, які ви десь отримуєте (сайт, вимірювальний прилад тощо) та середнє 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()

Що дає:

Значення та їх середнє значення як функція значень #


1

Ще одне рішення просто за допомогою стандартної бібліотеки та 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

1

Для освітніх цілей дозвольте мені додати ще два рішення "Numpy" (які повільніше, ніж розтягнення):

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


1

Усі вищезазначені рішення погані, оскільки їх не вистачає

  • швидкість завдяки нативному пітону замість нумерованої векторної реалізації,
  • чисельна стабільність через погане використання numpy.cumsum, або
  • швидкість за рахунок O(len(x) * w)реалізацій у вигляді згортків.

Дано

import numpy
m = 10000
x = numpy.random.rand(m)
w = 1000

Зауважимо, що x_[:w].sum()дорівнює x[:w-1].sum(). Отже, для першого середнього numpy.cumsum(...)додавання x[w] / w(через x_[w+1] / w) та віднімання 0x_[0] / w). Це призводить доx[0:w].mean()

Через сперму ви оновлюєте друге середнє, додаючи x[w+1] / wі віднімаючи x[0] / w, в результаті чого x[1:w+1].mean().

Це триває, поки не x[-w:].mean()буде досягнуто.

x_ = numpy.insert(x, 0, 0)
sliding_average = x_[:w].sum() / w + numpy.cumsum(x_[w:] - x_[:-w]) / w

Це рішення є векторним O(m), читабельним та чисельно стабільним.


1

Як щодо фільтра ковзних середніх ? Він також є однолінійним і має перевагу, що ви можете легко маніпулювати типом вікна, якщо вам потрібно щось інше, ніж прямокутник, тобто. N-довгий простий середній масив масиву:

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

І із застосованим трикутним вікном:

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

Примітка: я зазвичай відкидаю перші N зразків як хибні, отже, [N:]наприкінці, але це не обов'язково і справа лише в особистому виборі.


-7

Якщо ви вирішите скористатися власною, а не використовувати існуючу бібліотеку, будь ласка, пам’ятайте про помилку з плаваючою комою та намагайтеся мінімізувати її ефекти:

class SumAccumulator:
    def __init__(self):
        self.values = [0]
        self.count = 0

    def add( self, val ):
        self.values.append( val )
        self.count = self.count + 1
        i = self.count
        while i & 0x01:
            i = i >> 1
            v0 = self.values.pop()
            v1 = self.values.pop()
            self.values.append( v0 + v1 )

    def get_total(self):
        return sum( reversed(self.values) )

    def get_size( self ):
        return self.count

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


15
Це жахливо незрозуміла відповідь, хоча б якийсь коментар у коді чи пояснення того, чому це допомагає помилка з плаваючою комою, було б непогано.
Гейб

У своєму останньому реченні я намагався вказати, чому це допомагає помилці з плаваючою комою. Якщо два значення приблизно однакового порядку, то додавання їх втрачає меншу точність, ніж якщо ви додали дуже велике число до дуже маленького. Код поєднує "сусідні" значення таким чином, що навіть проміжні суми завжди повинні бути досить близькими за величиною, щоб мінімізувати помилку з плаваючою комою. Ніщо не є дурним доказом, але цей метод врятував пару дуже погано реалізованих проектів у виробництві.
Mayur Patel

1. Застосовуючи оригінальну задачу, це було б дуже повільно (обчислювальна середня величина), тому це просто не має значення 2. щоб страждати від проблеми точності 64-бітових чисел, треба підсумувати >> 2 ^ 30 майже рівні числа.
Аллео

@Alleo: Замість того, щоб робити одне додаткове значення, ви будете робити два. Доказ такий самий, як проблема біт-гортання. Однак суть цієї відповіді - це не обов’язково виконання, а точність. Використання пам'яті для усереднення 64-бітних значень не перевищує 64 елемента в кеші, тому воно дружнє і при використанні пам'яті.
Mayur Patel

Так, ви праві, що для цього потрібно 2 рази більше операцій, ніж проста сума, але первісною проблемою є обчислення середнього запуску , а не просто суми. Що можна зробити в O (n), але для вашої відповіді потрібно O (mn), де m - розмір вікна.
Аллео
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.