Ответы:
Для короткого, быстрого решения, которое делает все в одном цикле, без зависимостей, приведенный ниже код прекрасно работает.
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')
Скользящее среднее - это случай математической операции свертки . Для скользящего среднего вы перемещаете окно вдоль ввода и вычисляете среднее значение содержимого окна. Для дискретных одномерных сигналов свертка - это то же самое, за исключением того, что вместо среднего вы вычисляете произвольную линейную комбинацию, то есть умножаете каждый элемент на соответствующий коэффициент и суммируете результаты. Эти коэффициенты, по одному для каждой позиции в окне, иногда называют ядром свертки . Теперь среднее арифметическое значений 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
имеет большую сложность.
Свертка намного лучше, чем простой подход, но (я думаю) она использует БПФ и, следовательно, довольно медленно. Тем не менее, специально для вычисления среднего значения работает следующий подход
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 (мн); его документы упоминают, что scipy.signal.fftconvolve
использует БПФ.
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 в будущем. Чтобы использовать его, нам нужно сначала загрузить наши данные в Pandas Dataframe. Я хотел бы видеть эту функцию добавляемой либо numpy
или scipy.signal
.
%timeit bottleneck.move_mean(x, N)
в 3-15 раз быстрее, чем методы cumsum и pandas на моем компьютере. Взгляните на их тест в 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
. Реализация NumPy свертки включает в себя начальный переходный процесс, поэтому вы должны удалить первые 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.) Свернуть это самый медленный. (2.) сумма / вычитание примерно в 20-30 раз быстрее. (3. )iform_filter1d примерно в 2-3 раза быстрее, чем сумма / вычитание. Победитель определенно --iform_filter1d.
uniform_filter1d
это быстрее , чем cumsum
раствор (примерно 2-5x). и uniform_filter1d
не получает массивную ошибку с плавающей запятой, какcumsum
решение.
Я знаю, что это старый вопрос, но вот решение, которое не использует никаких дополнительных структур данных или библиотек. Он линейен по количеству элементов входного списка, и я не могу придумать какой-либо другой способ сделать его более эффективным (на самом деле, если кто-то знает, как лучше распределить результат, пожалуйста, дайте мне знать).
ПРИМЕЧАНИЕ: это было бы намного быстрее, если бы использовать пустой массив вместо списка, но я хотел устранить все зависимости. Также было бы возможно улучшить производительность многопоточным выполнением
Функция предполагает, что входной список является одномерным, поэтому будьте осторожны.
### 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)
«мм» - это скользящее среднее для «а».
«Окно» - это максимальное количество записей, которые нужно учитывать для скользящего среднего.
«min_count» - это минимальное количество записей, которое нужно учитывать для скользящего среднего (например, для первых нескольких элементов или если массив имеет значения nan).
Хорошая часть заключается в том, что «Узкое место» помогает справиться со значениями наночастиц, а также очень эффективно.
Я еще не проверил, насколько это быстро, но вы можете попробовать:
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
и выдает среднее значение по текущим значениям внутри окна. Он использует структуру данных 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
и т. Д. Они похожи, но отличаются.
В одном из ответов выше есть комментарий от mab, в котором есть этот метод. имеет простое скользящее среднее:bottleneck
move_mean
import numpy as np
import bottleneck as bn
a = np.arange(10) + np.random.random(10)
mva = bn.move_mean(a, window=2, min_count=1)
min_count
это удобный параметр, который в основном поднимает скользящее среднее до этой точки в вашем массиве. Если вы не установите min_count
, он будет равен window
, и все до window
очков будет nan
.
Еще один подход к поиску скользящей средней без использования numpy, панда
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.
Пример вывода (raw в черном, 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 (которые работают медленнее, чем решение cumsum):
import numpy as np
from numpy.lib.stride_tricks import as_strided
def ra_strides(arr, window):
''' Running average using as_strided'''
n = arr.shape[0] - window + 1
arr_strided = as_strided(arr, shape=[n, window], strides=2*arr.strides)
return arr_strided.mean(axis=1)
def ra_add(arr, window):
''' Running average using add.reduceat'''
n = arr.shape[0] - window + 1
indices = np.array([0, window]*n) + np.repeat(np.arange(n), 2)
arr = np.append(arr, 0)
return np.add.reduceat(arr, indices )[::2]/window
Используемые функции: as_strided , add.reduceat
Все вышеупомянутые решения плохие, потому что им не хватает
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()
Через cumsum вы обновите второе среднее, дополнительно сложив 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-длинная простая скользящая средняя массива a:
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
Если все ваши значения примерно одинакового порядка, это поможет сохранить точность, всегда добавляя значения примерно одинаковых величин.