Відповіді:
Для короткого швидкого рішення, яке робить все в один цикл, без залежностей, код нижче працює чудово.
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)
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()

numpy.cumsumмає кращі складності.
Згортання набагато краще, ніж прямолінійний підхід, але (я думаю) він використовує 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)
np.longdoubleале помилка з плаваючою комою все одно отримає істотну для відносно великої кількості очок (приблизно> 1e5, але залежить від ваших даних)numpy.convolveце O (mn); його документи згадують, що scipy.signal.fftconvolveвикористовує FFT.
running_mean([1,2,3], 2)дає array([1, 2]). Заміна xпо [float(value) for value in x]робить трюк.
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
Оновлення: У наведеному нижче прикладі показана стара 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
Також є приємні варіанти, як поводитися з крайовими значеннями.
df.rolling(windowsize).mean()тепер працює замість цього (дуже швидко я можу додати). за 6000 рядів рядів %timeit test1.rolling(20).mean()повернуто 1000 циклів, найкраще 3: 1,16 мс за цикл
df.rolling()працює досить добре, проблема полягає в тому, що навіть ця форма не підтримуватиме ndarrays у майбутньому. Для його використання нам доведеться спочатку завантажити наші дані в Панель даних Панди. Я хотів би, щоб ця функція була додана до numpyабо scipy.signal.
%timeit bottleneck.move_mean(x, N)в 3 - 15 разів швидше, ніж методи сперми та панди на моєму ПК. Подивіться на їх еталоном РЕПО в README .
Ви можете обчислити середнє значення ходу за допомогою:
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'режим, який, здається, повинен вирішувати стартову проблему, що переходить, але розбиває її між початком і кінцем.
mode='valid'в convolveякому не потрібно жодної післяобробки.
mode='valid'видаляє перехідне з обох кінців, правда? Якщо len(x)=10і N=4, для середнього запуску, я хотів би 10 результатів, але validповертається 7.
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).
runningMeanЧи є у мене побічний ефект усереднення нулів, коли ви виходите з масиву з x[ctr:(ctr+N)]правою стороною масиву.
runningMeanFastтакож це питання ефекту кордону.
або модуль для 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. Я отримую цю помилку, сер.
Про готове до використання рішення див. 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)
numpy.convolveрізницю лише в зміні послідовності.
wрозмір вікна та sдані?
Ви можете використовувати 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)]
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 робить рішення.
Я знаю, що це старе питання, але ось рішення, яке не використовує зайвих структур даних або бібліотек. Він лінійний за кількістю елементів вхідного списку, і я не можу придумати іншого способу зробити його більш ефективним (насправді, якщо хтось знає про кращий спосіб розподілу результату, будь ласка, дайте мені знати).
ПРИМІТКА. Це було б набагато швидше, використовуючи масив 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].
Я вважаю, що це можна елегантно вирішити за допомогою вузького місця
Дивіться базовий зразок нижче:
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 допомагає боротися з значеннями нана, і це також дуже ефективно.
Я ще не перевірив, наскільки це швидко, але ви можете спробувати:
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)
Ця відповідь містить рішення, що використовують стандартну бібліотеку 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
Трохи запізнюючись на вечірку, але я зробив власну маленьку функцію, яка НЕ обертається навколо кінців або колодок з нулями, які потім використовуються і для пошуку середнього. В якості подальшої трактування, він також повторно відбирає сигнал у лінійно розташованих точках. Налаштуйте код за бажанням, щоб отримати інші функції.
Метод - це просте множення матриць з нормалізованим ядром Гаусса.
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
Просте використання на синусоїдальному сигналі з додаванням нормального розподіленого шуму:

sum, використовуючи np.sumзамість 2 The @оператора (поняття не маю , що це таке) видає повідомлення про помилку. Я можу поглянути на це пізніше, але мені зараз бракує часу
Замість нумети чи схипі я рекомендую пандам зробити це швидше:
df['data'].rolling(3).mean()
Це займає ковзну середню (MA) 3 періоди стовпця "дані". Ви також можете обчислити зміщені версії, наприклад, ту, що виключає поточну комірку (зміщену одну назад), можна легко обчислити як:
df['data'].shift(periods=1).rolling(3).mean()
pandas.rolling_meanа мінне pandas.DataFrame.rolling. Ви також можете легко розрахувати переміщення min(), max(), sum()тощо, а також за mean()допомогою цього методу.
pandas.rolling_min, pandas.rolling_maxтощо. Вони схожі, але різні.
Є коментар МАБ похований в одній з відповідей, над якою є цей метод. 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.
Інший підхід до пошуку ковзної середньої без використання 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]
Це питання зараз навіть старше, ніж коли 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, однак, я вважаю, що ідея стоїть.
Вище є багато відповідей щодо обчислення середнього бігу. Моя відповідь додає дві додаткові функції:
Ця друга особливість особливо корисна для визначення, які значення відрізняються від загальної тенденції певною сумою.
Я використовую 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 синім кольором):

Цей код може бути легко адаптований для видалення всіх ковзних середніх значень, обчислених від менших, ніж відріз = 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)
Використовувати лише стандартну бібліотеку 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 чомусь, може хтось коментує реалізацію шахти та стандартного документа?
O(n*d) розрахунки ( dякщо розмір вікна, nрозмір ітерабельного), і вони роблятьO(n)
Хоча тут є рішення цього питання, будь ласка, погляньте на моє рішення. Це дуже просто і добре працює.
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)
Читаючи інші відповіді, я не думаю, що це питання, про яке я задався, але я потрапив сюди з необхідністю зберігати середнє значення списку значень, що збільшувався в розмірах.
Отже, якщо ви хочете зберегти список значень, які ви десь отримуєте (сайт, вимірювальний прилад тощо) та середнє 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()
Що дає:
Ще одне рішення просто за допомогою стандартної бібліотеки та 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
Для освітніх цілей дозвольте мені додати ще два рішення "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
Усі вищезазначені рішення погані, оскільки їх не вистачає
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) та віднімання 0(з x_[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), читабельним та чисельно стабільним.
Як щодо фільтра ковзних середніх ? Він також є однолінійним і має перевагу, що ви можете легко маніпулювати типом вікна, якщо вам потрібно щось інше, ніж прямокутник, тобто. N-довгий простий середній масив масиву:
lfilter(np.ones(N)/N, [1], a)[N:]
І із застосованим трикутним вікном:
lfilter(np.ones(N)*scipy.signal.triang(N)/N, [1], a)[N:]
Примітка: я зазвичай відкидаю перші N зразків як хибні, отже, [N:]наприкінці, але це не обов'язково і справа лише в особистому виборі.
Якщо ви вирішите скористатися власною, а не використовувати існуючу бібліотеку, будь ласка, пам’ятайте про помилку з плаваючою комою та намагайтеся мінімізувати її ефекти:
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
Якщо всі ваші значення мають приблизно однаковий порядок, то це допоможе зберегти точність, завжди додаючи значення приблизно однакових величин.