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