Respostas:
Para uma solução curta e rápida que faz a coisa toda em um loop, sem dependências, o código abaixo funciona muito bem.
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: soluções mais eficientes foram propostas por Alleo e jasaarim .
Você pode usar np.convolve
para isso:
np.convolve(x, np.ones((N,))/N, mode='valid')
A média corrente é um caso da operação matemática da convolução . Para a média em execução, você desliza uma janela ao longo da entrada e calcula a média do conteúdo da janela. Para sinais 1D discretos, convolução é a mesma coisa, exceto que, em vez da média, você calcula uma combinação linear arbitrária, ou seja, multiplica cada elemento por um coeficiente correspondente e soma os resultados. Esses coeficientes, um para cada posição na janela, às vezes são chamados de kernel de convolução . Agora, a média aritmética de N valores é (x_1 + x_2 + ... + x_N) / N
, então o kernel correspondente é (1/N, 1/N, ..., 1/N)
, e é exatamente isso que obtemos usando np.ones((N,))/N
.
O mode
argumento de np.convolve
especifica como lidar com as arestas. Eu escolhi o valid
modo aqui porque acho que é assim que a maioria das pessoas espera que a corrida funcione, mas você pode ter outras prioridades. Aqui está um gráfico que ilustra a diferença entre os modos:
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
tem uma complexidade melhor.
A convolução é muito melhor do que a abordagem direta, mas (eu acho) ela usa FFT e, portanto, bastante lenta. No entanto, especialmente para calcular a corrida, a seguinte abordagem funciona bem
def running_mean(x, N):
cumsum = numpy.cumsum(numpy.insert(x, 0, 0))
return (cumsum[N:] - cumsum[:-N]) / float(N)
O código para verificar
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
Note-se que numpy.allclose(result1, result2)
é True
, dois métodos são equivalentes. Quanto maior o N, maior a diferença no tempo.
# 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
mas seu erro de ponto flutuante ainda será significativo para um número relativamente grande de pontos (em torno de> 1e5, mas depende dos seus dados)numpy.convolve
O (mn); seus documentos mencionam que scipy.signal.fftconvolve
usa FFT.
running_mean([1,2,3], 2)
gives array([1, 2])
. Substituir x
por [float(value) for value in x]
faz o truque.
x
contiver flutuadores. Exemplo: running_mean(np.arange(int(1e7))[::-1] + 0.2, 1)[-1] - 0.2
retorna 0.003125
enquanto se espera 0.0
. Mais informações: en.wikipedia.org/wiki/Loss_of_significance
Atualização: O exemplo abaixo mostra a pandas.rolling_mean
função antiga que foi removida nas versões recentes do pandas. Um equivalente moderno da chamada de função abaixo seria
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])
pandas é mais adequado para isso do que o NumPy ou SciPy. Sua função rolling_mean faz o trabalho de maneira conveniente. Ele também retorna uma matriz NumPy quando a entrada é uma matriz.
É difícil superar o rolling_mean
desempenho com qualquer implementação Python pura e personalizada. Aqui está um exemplo de desempenho em relação a duas das soluções propostas:
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
Também existem boas opções de como lidar com os valores das arestas.
df.rolling(windowsize).mean()
agora funciona (em vez disso, devo acrescentar muito rapidamente). para 6.000 séries de linhas %timeit test1.rolling(20).mean()
retornou 1000 loops, o melhor de 3: 1,16 ms por loop
df.rolling()
funciona bem o suficiente, o problema é que mesmo esse formulário não suportará ndarrays no futuro. Para usá-lo, teremos que carregar nossos dados em um Dataframe do Pandas primeiro. Eu adoraria ver essa função adicionada a um numpy
ou a outro scipy.signal
.
%timeit bottleneck.move_mean(x, N)
é de 3 a 15 vezes mais rápido que os métodos cumsum e pandas no meu pc. Dê uma olhada em sua referência no README do repo .
Você pode calcular uma média de execução com:
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
Mas é lento.
Felizmente, o numpy inclui uma função convolve que podemos usar para acelerar as coisas. A média de execução é equivalente a convolver x
com um vetor que é N
longo, com todos os membros iguais a 1/N
. A implementação numpy do convolve inclui o transiente inicial, portanto, você deve remover os primeiros pontos N-1:
def runningMeanFast(x, N):
return np.convolve(x, np.ones((N,))/N)[(N-1):]
Na minha máquina, a versão rápida é 20 a 30 vezes mais rápida, dependendo do comprimento do vetor de entrada e do tamanho da janela de média.
Observe que o convolve inclui um 'same'
modo que parece que deveria resolver o problema transitório inicial, mas o divide entre o começo e o fim.
mode='valid'
em convolve
que não requer qualquer pós-processamento.
mode='valid'
remove o transitório de ambas as extremidades, certo? Se len(x)=10
e N=4
, para uma corrida significa que eu gostaria 10 resultados, mas valid
retorna 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')
(com pyplot e numpy importados).
runningMean
Tenho o efeito colateral da média com zeros, quando você sai da matriz com x[ctr:(ctr+N)]
o lado direito da matriz.
runningMeanFast
também tem esse problema de efeito de fronteira.
ou módulo para python que calcula
nos meus testes no Tradewave.net, o TA-lib sempre vence:
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])
resultados:
[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
. Estou recebendo esse erro, senhor.
Para uma solução pronta para uso, consulte https://scipy-cookbook.readthedocs.io/items/SignalSmooth.html . Ele fornece média de execução com o flat
tipo de janela. Observe que isso é um pouco mais sofisticado do que o método simples de convolução do tipo faça você mesmo, pois ele tenta lidar com os problemas no início e no final dos dados refletindo-os (o que pode ou não funcionar no seu caso). ..)
Para começar, você pode tentar:
a = np.random.random(100)
plt.plot(a)
b = smooth(a, window='flat')
plt.plot(b)
numpy.convolve
, a diferença apenas na alteração da sequência.
w
o tamanho da janela e s
os dados?
Você pode usar 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'
está o padrão, mas no meu caso, eu queria'nearest'
Também é bastante rápido (quase 50 vezes mais rápido np.convolve
e 2-5 vezes mais rápido que a abordagem de cumsum fornecida acima ):
%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
aqui estão três funções que permitem comparar erros / velocidades de diferentes implementações:
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
com um retângulo, e np.cumsum
seguido por np.subtract
. meus resultados: (1.) convolve é o mais lento. (2.) cumsum / subtrair é cerca de 20 a 30 vezes mais rápido. (3.) uniform_filter1d é cerca de 2-3 vezes mais rápido que cumsum / subtrair. vencedor é definitivamente uniform_filter1d.
uniform_filter1d
é mais rápido que a cumsum
solução (em cerca de 2-5x). e uniform_filter1d
não obtém erro maciço de ponto flutuante como acumsum
solução.
Sei que essa é uma pergunta antiga, mas aqui está uma solução que não usa nenhuma estrutura ou biblioteca de dados extra. É linear no número de elementos da lista de entrada e não consigo pensar em outra maneira de torná-la mais eficiente (na verdade, se alguém souber uma maneira melhor de alocar o resultado, informe-me).
NOTA: isso seria muito mais rápido usando uma matriz numpy em vez de uma lista, mas eu queria eliminar todas as dependências. Também seria possível melhorar o desempenho através da execução multithread
A função assume que a lista de entrada é unidimensional, portanto, tenha cuidado.
### 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
Exemplo
Suponha que temos uma lista data = [ 1, 2, 3, 4, 5, 6 ]
na qual queremos calcular uma média móvel com período de 3 e que você também deseja uma lista de saída com o mesmo tamanho da entrada (na maioria das vezes).
O primeiro elemento possui o índice 0, portanto a média móvel deve ser calculada nos elementos do índice -2, -1 e 0. Obviamente, não temos dados [-2] e dados [-1] (a menos que você queira usar métodos especiais). condições de contorno), então assumimos que esses elementos são 0. Isso equivale a preencher com zero a lista, exceto que na verdade não a preenchemos, basta acompanhar os índices que exigem preenchimento (de 0 a N-1).
Assim, para os primeiros N elementos, continuamos somando os elementos em um acumulador.
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
A partir dos elementos N + 1, a acumulação simples não funciona. esperamos, result[3] = (2 + 3 + 4)/3 = 3
mas isso é diferente de (sum + 4)/3 = 3.333
.
A maneira de calcular o valor correto é subtrair data[0] = 1
de sum+4
, dando assim sum + 4 - 1 = 9
.
Isso acontece porque atualmente sum = data[0] + data[1] + data[2]
, mas também é verdade para todos i >= N
porque, antes da subtração, sum
é data[i-N] + ... + data[i-2] + data[i-1]
.
Eu sinto que isso pode ser resolvido de forma elegante usando gargalo
Veja a amostra básica abaixo:
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 média móvel de "a".
"window" é o número máximo de entradas a serem consideradas para a média móvel.
"min_count" é o número mínimo de entradas a serem consideradas para a média móvel (por exemplo, para os primeiros elementos ou se a matriz possui valores nan).
A parte boa é que o gargalo ajuda a lidar com os valores nan e também é muito eficiente.
Ainda não verifiquei o quão rápido isso é, mas você pode tentar:
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)
Esta resposta contém soluções usando a biblioteca padrão do Python para três cenários diferentes.
itertools.accumulate
Esta é uma solução Python 3.2+ com eficiência de memória que calcula a média de execução em uma quantidade iterável de valores ao aproveitar itertools.accumulate
.
>>> from itertools import accumulate
>>> values = range(100)
Observe que values
pode ser iterável, incluindo geradores ou qualquer outro objeto que produza valores em tempo real.
Primeiro, construa preguiçosamente a soma cumulativa dos valores.
>>> cumu_sum = accumulate(value_stream)
Em seguida, enumerate
a soma cumulativa (começando em 1) e construa um gerador que produz a fração dos valores acumulados e o índice de enumeração atual.
>>> rolling_avg = (accu/i for i, accu in enumerate(cumu_sum, 1))
Você pode emitir means = list(rolling_avg)
se precisar de todos os valores na memória de uma vez ou ligar de forma next
incremental.
(Obviamente, você também pode iterar rolling_avg
com um for
loop, que será chamado next
implicitamente.)
>>> next(rolling_avg) # 0/1
>>> 0.0
>>> next(rolling_avg) # (0 + 1)/2
>>> 0.5
>>> next(rolling_avg) # (0 + 1 + 2)/3
>>> 1.0
Esta solução pode ser escrita como uma função da seguinte maneira.
from itertools import accumulate
def rolling_avg(iterable):
cumu_sum = accumulate(iterable)
yield from (accu/i for i, accu in enumerate(cumu_sum, 1))
Essa rotina consome os valores que você envia e mantém uma média contínua dos valores vistos até o momento.
É útil quando você não possui valores iteráveis, mas adquire os valores a serem calculados em média um a um em momentos diferentes ao longo da vida do programa.
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
A corotina funciona assim:
>>> 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
Essa função de gerador pega um iterável e um tamanho de janela N
e gera a média sobre os valores atuais dentro da janela. Ele usa a deque
, que é uma estrutura de dados semelhante a uma lista, mas otimizada para modificações rápidas ( pop
, append
) nos dois pontos de extremidade .
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)
Aqui está a função em ação:
>>> 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
Um pouco atrasado para a festa, mas criei minha própria função que NÃO envolve as extremidades ou os zeros com zeros que são usados para encontrar a média também. Como um tratamento adicional, é que ele também faz nova amostragem do sinal em pontos espaçados linearmente. Personalize o código à vontade para obter outros recursos.
O método é uma multiplicação simples de matriz com um kernel Gaussiano normalizado.
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
Um uso simples em um sinal sinusoidal com ruído distribuído normal adicionado:
sum
, usando em np.sum
vez disso 2 O @
operador (não faz ideia do que é isso) gera um erro. Eu posso olhar para ele mais tarde, mas eu estou faltando o tempo agora
Em vez de entorpecido ou covarde, eu recomendaria que os pandas fizessem isso mais rapidamente:
df['data'].rolling(3).mean()
Isso leva a média móvel (MA) de 3 períodos da coluna "dados". Você também pode calcular as versões deslocadas, por exemplo, a que exclui a célula atual (deslocada uma para trás) pode ser calculada facilmente como:
df['data'].shift(periods=1).rolling(3).mean()
pandas.rolling_mean
enquanto a mina usa pandas.DataFrame.rolling
. Você também pode calcular o movimento, min(), max(), sum()
etc., bem como mean()
com esse método facilmente.
pandas.rolling_min, pandas.rolling_max
etc. Eles são semelhantes, mas diferentes.
Há um comentário de mab enterrado em uma das respostas acima, que possui esse método. bottleneck
tem move_mean
uma média móvel simples:
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
é um parâmetro útil que basicamente levará a média móvel até esse ponto em sua matriz. Se você não definir min_count
, ele irá igualar window
, e tudo até window
pontos será nan
.
Outra abordagem para encontrar a média móvel sem usar o numpy, o panda
import itertools
sample = [2, 6, 10, 8, 11, 10]
list(itertools.starmap(lambda a,b: b/a,
enumerate(itertools.accumulate(sample), 1)))
imprimirá [2.0, 4.0, 6.0, 6.5, 7.4, 7.833333333333333]
Agora, essa pergunta é ainda mais antiga do que quando o NeXuS escreveu sobre isso no mês passado, mas eu gosto de como o código dele lida com casos extremos. No entanto, por ser uma "média móvel simples", seus resultados ficam aquém dos dados aos quais se aplicam. Pensei que lidar com casos extremos de uma forma mais satisfatória do que os modos de Numpy valid
, same
e full
poderia ser alcançado através da aplicação de uma abordagem similar a um convolution()
método baseado.
Minha contribuição usa uma média de execução central para alinhar seus resultados com seus dados. Quando há muito poucos pontos disponíveis para a janela de tamanho completo a ser usada, as médias de execução são calculadas a partir de janelas sucessivamente menores nas bordas da matriz. [Na verdade, a partir de janelas sucessivamente maiores, mas esse é um detalhe da implementação.]
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]])
É relativamente lento porque usa convolve()
, e provavelmente poderia ser bastante estimulado por um verdadeiro Pythonista, no entanto, acredito que a idéia se mantém.
Há muitas respostas acima sobre o cálculo de uma média corrente. Minha resposta adiciona dois recursos extras:
Esse segundo recurso é particularmente útil para determinar quais valores diferem da tendência geral em um determinado valor.
Uso numpy.cumsum, pois é o método mais econômico em termos de tempo ( consulte a resposta de Alleo acima ).
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)
Esse código funciona apenas para Ns. Pode ser ajustado para números ímpares, alterando o np.insert de padded_x e n_nan.
Exemplo de saída (bruto em preto, movavg em azul):
Esse código pode ser facilmente adaptado para remover todos os valores médios móveis calculados com menos que cutoff = 3 valores não nan.
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)
Use apenas a biblioteca padrão do Python (memória eficiente)
Apenas dê outra versão do uso deque
apenas da biblioteca padrão . É uma surpresa para mim que a maioria das respostas esteja usando pandas
ou 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]
Na verdade, eu encontrei outra implementação em documentos 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
No entanto, a implementação me parece um pouco mais complexa do que deveria ser. Mas deve estar nos documentos python padrão por um motivo: alguém poderia comentar sobre a implementação do meu e do documento padrão?
O(n*d)
cálculos ( d
sendo o tamanho da janela, n
tamanho do iterable) e eles estão fazendoO(n)
Embora existam soluções para esta pergunta aqui, dê uma olhada na minha solução. É muito simples e está funcionando bem.
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)
Ao ler as outras respostas, acho que não é isso que a pergunta pediu, mas cheguei aqui com a necessidade de manter uma média constante de uma lista de valores que cresciam em tamanho.
Portanto, se você quiser manter uma lista dos valores que está adquirindo de algum lugar (um site, um dispositivo de medição etc.) e a média dos últimos n
valores atualizados, use o código abaixo, para minimizar o esforço de adicionar novos elementos:
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)
E você pode testá-lo com, por exemplo:
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()
Que dá:
Outra solução usando apenas uma biblioteca padrão e 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
Para fins educacionais, deixe-me adicionar mais duas soluções Numpy (que são mais lentas que a solução 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
Funções usadas: as_strided , add.reduceat
Todas as soluções acima mencionadas são pobres porque não possuem
numpy.cumsum
ouO(len(x) * w)
implementações como convoluções.Dado
import numpy
m = 10000
x = numpy.random.rand(m)
w = 1000
Note que x_[:w].sum()
é igual x[:w-1].sum()
. Portanto, para a primeira média, as numpy.cumsum(...)
adições x[w] / w
(via x_[w+1] / w
) e subtrações 0
(de x_[0] / w
). Isto resulta emx[0:w].mean()
Por meio do cumsum, você atualizará a segunda média adicionando x[w+1] / w
e subtraindo adicionalmente x[0] / w
, resultando emx[1:w+1].mean()
.
Isso continua até que x[-w:].mean()
seja alcançado.
x_ = numpy.insert(x, 0, 0)
sliding_average = x_[:w].sum() / w + numpy.cumsum(x_[w:] - x_[:-w]) / w
Esta solução é vetorizada O(m)
, legível e numericamente estável.
Que tal um filtro de média móvel ? É também uma linha e tem a vantagem de poder manipular facilmente o tipo de janela se precisar de algo além do retângulo, por exemplo. uma média móvel simples N-longa de uma matriz a:
lfilter(np.ones(N)/N, [1], a)[N:]
E com a janela triangular aplicada:
lfilter(np.ones(N)*scipy.signal.triang(N)/N, [1], a)[N:]
Nota: Normalmente, descarto as primeiras N amostras como falsas, portanto, [N:]
no final, mas não é necessário e é apenas uma questão de escolha pessoal.
Se você optar por criar o seu próprio, em vez de usar uma biblioteca existente, esteja ciente do erro de ponto flutuante e tente minimizar seus efeitos:
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
Se todos os seus valores forem aproximadamente da mesma ordem de magnitude, isso ajudará a preservar a precisão, sempre adicionando valores de magnitudes aproximadamente semelhantes.