Como calcular a média móvel usando NumPy?

109

Parece não haver função que simplesmente calcule a média móvel em numpy / scipy, levando a soluções complicadas .

Minha pergunta é dupla:

  • Qual é a maneira mais fácil de (corretamente) implementar uma média móvel com numpy?
  • Já que isso parece não trivial e sujeito a erros, há um bom motivo para não ter as baterias incluídas neste caso?
goncalopp
fonte
19
A solução da convolução não me parece tão complicada!
wim
4
A média móvel não é apenas um filtro passa-baixa (ou seja, 'desfoque')? Tenho certeza de que esse é exatamente o tipo de coisa para a qual a convolução se destina ...
user541686
@mmgp Acho que esperava estar errado, ou que havia um motivo bom e óbvio.
goncalopp
3
@wim Era meio que um trocadilho. Mas o mero fato de que a questão existe significa que não é fácil criar uma média móvel a partir de numpy.convolute.
goncalopp
3
Possível duplicata da média móvel ou média móvel
pppery

Respostas:

165

Se você quer apenas um simples não ponderada média móvel, você pode facilmente implementá-lo com np.cumsum, que pode ser é métodos mais rápido do que FFT base:

EDIT Corrigido uma indexação errada por um ponto detectada por Bean no código. EDITAR

def moving_average(a, n=3) :
    ret = np.cumsum(a, dtype=float)
    ret[n:] = ret[n:] - ret[:-n]
    return ret[n - 1:] / n

>>> a = np.arange(20)
>>> moving_average(a)
array([  1.,   2.,   3.,   4.,   5.,   6.,   7.,   8.,   9.,  10.,  11.,
        12.,  13.,  14.,  15.,  16.,  17.,  18.])
>>> moving_average(a, n=4)
array([  1.5,   2.5,   3.5,   4.5,   5.5,   6.5,   7.5,   8.5,   9.5,
        10.5,  11.5,  12.5,  13.5,  14.5,  15.5,  16.5,  17.5])

Portanto, acho que a resposta é: é realmente fácil de implementar e talvez o numpy já esteja um pouco inchado com funcionalidades especializadas.

Jaime
fonte
10
Este código está incorreto. por exemplo, movendo_média ([1,2,5,10], n = 2) dá [1., 3,5, 8,5]. Mesmo o caso de teste do respondente para uma média móvel de valores de 0 a 19 está incorreto, alegando que a média de 0, 1 e 2 é 0,5. Como conseguiu 6 votos positivos?
JeremyKun
2
Obrigado pela verificação de bug, agora parece estar funcionando bem. Quanto aos votos positivos, acho que a ideia geral por trás da resposta pesou mais do que um erro aleatório na implementação, mas quem sabe.
Jaime
2
Eu achei o problema. ret[n:] -= ret[:-n]NÃO é o mesmo que ret[n:] = ret[n:] - ret[:-n]. Corrigi o código nesta resposta. Edit: Não, alguém acabou de chegar antes de mim.
Timmmm
7
@Timmmm eu fiz, esse era realmente o problema. O princípio geral por trás dessa resposta é amplamente usado no processamento de imagens (tabelas de áreas somadas, como eles chamam), então o problema tinha que estar na implementação. Um bom exemplo de ser bit por otimização prematura, já que me lembro de fazer a operação no local "porque será mais eficiente". Pelo lado bom, provavelmente produziu a resposta errada mais rápido ...
Jaime
43
Hmmm, parece que essa função "fácil de implementar" é realmente muito fácil de errar e gerou uma boa discussão sobre a eficiência da memória. Fico feliz por ter inchado se isso significar saber que algo foi feito da maneira certa.
Richard de
81

A falta de uma função específica de domínio particular do NumPy talvez se deva à disciplina e fidelidade da equipe principal à diretiva principal do NumPy: fornecer um tipo de array N-dimensional , bem como funções para criar e indexar esses arrays. Como muitos objetivos básicos, este não é pequeno e o NumPy o faz de maneira brilhante.

O (muito) maior SciPy contém uma coleção muito maior de bibliotecas específicas de domínio (chamadas de subpacotes pelos desenvolvedores SciPy) - por exemplo, otimização numérica ( otimizar ), processamento de sinal ( sinal ) e cálculo integral ( integrar ).

Meu palpite é que a função que você está procurando está em pelo menos um dos subpacotes SciPy ( scipy.signal talvez); no entanto, eu procuraria primeiro na coleção de scikits SciPy , identificaria os scikits relevantes e procuraria a função de interesse lá.

Scikits são pacotes desenvolvidos de forma independente com base em NumPy / SciPy e direcionados a uma disciplina técnica específica (por exemplo, scikits-image , scikits-learn , etc.). Vários deles (em particular, o incrível OpenOpt para otimização numérica) eram altamente considerados, projetos maduros muito antes de escolher residir sob a rubrica scikits relativamente nova . A página inicial do Scikits gostou de listar cerca de 30 desses scikits , embora pelo menos vários deles não estejam mais em desenvolvimento ativo.

Seguir esse conselho o levaria à série scikits-time ; no entanto, esse pacote não está mais em desenvolvimento ativo; Na verdade, Pandas se tornou, AFAIK, a biblioteca de série temporal baseada em NumPy de fato .

O Pandas possui várias funções que podem ser usadas para calcular uma média móvel ; o mais simples deles é provavelmente rolling_mean , que você usa assim:

>>> # the recommended syntax to import pandas
>>> import pandas as PD
>>> import numpy as NP

>>> # prepare some fake data:
>>> # the date-time indices:
>>> t = PD.date_range('1/1/2010', '12/31/2012', freq='D')

>>> # the data:
>>> x = NP.arange(0, t.shape[0])

>>> # combine the data & index into a Pandas 'Series' object
>>> D = PD.Series(x, t)

Agora, basta chamar a função rolling_mean passando no objeto Series e um tamanho de janela , que no meu exemplo abaixo é de 10 dias .

>>> d_mva = PD.rolling_mean(D, 10)

>>> # d_mva is the same size as the original Series
>>> d_mva.shape
    (1096,)

>>> # though obviously the first w values are NaN where w is the window size
>>> d_mva[:3]
    2010-01-01         NaN
    2010-01-02         NaN
    2010-01-03         NaN

verifique se funcionou - por exemplo, valores comparados 10-15 na série original versus a nova série suavizada com média móvel

>>> D[10:15]
     2010-01-11    2.041076
     2010-01-12    2.041076
     2010-01-13    2.720585
     2010-01-14    2.720585
     2010-01-15    3.656987
     Freq: D

>>> d_mva[10:20]
      2010-01-11    3.131125
      2010-01-12    3.035232
      2010-01-13    2.923144
      2010-01-14    2.811055
      2010-01-15    2.785824
      Freq: D

A função rolling_mean, junto com cerca de uma dúzia ou mais de outras funções, são agrupadas informalmente na documentação do Pandas sob a rubrica funções de janela móvel ; um segundo grupo relacionado de funções no Pandas é referido como funções exponencialmente ponderadas (por exemplo, ewma , que calcula a média ponderada exponencialmente móvel). O fato de que este segundo grupo não está incluído no primeiro ( funções de janela móvel ) é talvez porque as transformações exponencialmente ponderadas não dependem de uma janela de comprimento fixo

doug
fonte
6
O Pandas tem uma linha forte de funções de janela móvel. Mas me parece um pouco sobrecarga demais para uma média móvel simples.
Jaime
6
bem, eu duvido que o cálculo de uma média móvel seja um requisito isolado para o OP ou para qualquer outra pessoa. Se você precisar calcular uma média móvel, é quase certo que você tenha uma série temporal, o que significa que precisa de uma estrutura de dados que permita conformar um índice de data e hora aos seus dados e esse é o 'overhead' ao qual você se refere.
doug de
2
Em primeiro lugar, obrigado por dedicar seu tempo para escrever esta resposta extremamente informativa. Na verdade, não consigo ver uma utilidade para uma média móvel que não envolva uma série temporal. Mas isso não significa que é necessário conformar-se a uma data e hora, ou mesmo a um intervalo de amostragem particular (pode ser desconhecido)
goncalopp
3
Só queria acrescentar que a função de média móvel foi extraída para a biblioteca Gargalo se os pandas parecerem muito pesados ​​como uma dependência.
robochat
4
'rolling_mean' não é mais parte pf pandas, por favor, veja a resposta usando 'rolling' ao invés
Vladtn
59

Uma maneira simples de conseguir isso é usando np.convolve. A ideia por trás disso é alavancar a forma como a convolução discreta é calculada e usá-la para retornar uma média móvel . Isso pode ser feito por convolução com uma sequência de np.onescomprimento igual ao comprimento da janela deslizante que desejamos.

Para fazer isso, podemos definir a seguinte função:

def moving_average(x, w):
    return np.convolve(x, np.ones(w), 'valid') / w

Esta função tomará a convolução da sequência xe uma sequência de unidades de comprimento w. Observe que o escolhido modeé validpara que o produto da convolução seja dado apenas para pontos onde as sequências se sobrepõem completamente.


Alguns exemplos:

x = np.array([5,3,8,10,2,1,5,1,0,2])

Para uma média móvel com uma janela de comprimento 2teríamos:

moving_average(x, 2)
# array([4. , 5.5, 9. , 6. , 1.5, 3. , 3. , 0.5, 1. ])

E para uma janela de comprimento 4:

moving_average(x, 4)
# array([6.5 , 5.75, 5.25, 4.5 , 2.25, 1.75, 2.  ])

Como convolvefunciona?

Vamos dar uma olhada mais aprofundada na maneira como a convolução discreta está sendo calculada. A função a seguir tem como objetivo replicar a maneira como np.convolveestá computando os valores de saída:

def mov_avg(x, w):
    for m in range(len(x)-(w-1)):
        yield sum(np.ones(w) * x[m:m+w]) / w 

Que, para o mesmo exemplo acima, também resultaria:

list(mov_avg(x, 2))
# [4.0, 5.5, 9.0, 6.0, 1.5, 3.0, 3.0, 0.5, 1.0]

Portanto, o que está sendo feito em cada etapa é pegar o produto interno entre a matriz de uns e a janela atual . Nesse caso, a multiplicação por np.ones(w)é supérflua, visto que estamos tirando diretamente o sumda sequência.

Abaixo está um exemplo de como as primeiras saídas são calculadas para que sejam um pouco mais claras. Vamos supor que queremos uma janela de w=4:

[1,1,1,1]
[5,3,8,10,2,1,5,1,0,2]
= (1*5 + 1*3 + 1*8 + 1*10) / w = 6.5

E a seguinte saída seria calculada como:

  [1,1,1,1]
[5,3,8,10,2,1,5,1,0,2]
= (1*3 + 1*8 + 1*10 + 1*2) / w = 5.75

E assim por diante, retornando uma média móvel da sequência depois que todas as sobreposições forem realizadas.

yatu
fonte
Esta é uma boa ideia! É mais rápido do que a resposta de @Jaime para n pequeno, mas se torna mais lento para n maior.
Felipe Gerard
Obrigado @FelipeGerard! Sim, conforme apontado nos comentários, embora esta abordagem possa não ser tão eficiente quanto algumas outras soluções numpy, imo é bom ter uma alternativa para futuros visitantes devido à sua simplicidade e concisão
yatu
Às vezes é útil ter uma matriz de saída do mesmo tamanho da entrada. Para isso, o mode='valid'pode ser substituído por 'same'. Apenas neste caso os pontos de borda gravitarão em torno de zero.
Ilia Barahovski 01 de
15

Aqui estão uma variedade de maneiras de fazer isso, junto com alguns benchmarks. Os melhores métodos são versões que usam código otimizado de outras bibliotecas. O bottleneck.move_meanmétodo provavelmente é o melhor. A scipy.convolveabordagem também é muito rápida, extensível e sintática e conceitualmente simples, mas não é escalonável para valores de janela muito grandes. O numpy.cumsummétodo é bom se você precisar de uma numpyabordagem pura .

Nota: Alguns destes (por exemplo bottleneck.move_mean) não são centralizados e irão deslocar seus dados.

import numpy as np
import scipy as sci
import scipy.signal as sig
import pandas as pd
import bottleneck as bn
import time as time

def rollavg_direct(a,n): 
    'Direct "for" loop'
    assert n%2==1
    b = a*0.0
    for i in range(len(a)) :
        b[i]=a[max(i-n//2,0):min(i+n//2+1,len(a))].mean()
    return b

def rollavg_comprehension(a,n):
    'List comprehension'
    assert n%2==1
    r,N = int(n/2),len(a)
    return np.array([a[max(i-r,0):min(i+r+1,N)].mean() for i in range(N)]) 

def rollavg_convolve(a,n):
    'scipy.convolve'
    assert n%2==1
    return sci.convolve(a,np.ones(n,dtype='float')/n, 'same')[n//2:-n//2+1]  

def rollavg_convolve_edges(a,n):
    'scipy.convolve, edge handling'
    assert n%2==1
    return sci.convolve(a,np.ones(n,dtype='float'), 'same')/sci.convolve(np.ones(len(a)),np.ones(n), 'same')  

def rollavg_cumsum(a,n):
    'numpy.cumsum'
    assert n%2==1
    cumsum_vec = np.cumsum(np.insert(a, 0, 0)) 
    return (cumsum_vec[n:] - cumsum_vec[:-n]) / n

def rollavg_cumsum_edges(a,n):
    'numpy.cumsum, edge handling'
    assert n%2==1
    N = len(a)
    cumsum_vec = np.cumsum(np.insert(np.pad(a,(n-1,n-1),'constant'), 0, 0)) 
    d = np.hstack((np.arange(n//2+1,n),np.ones(N-n)*n,np.arange(n,n//2,-1)))  
    return (cumsum_vec[n+n//2:-n//2+1] - cumsum_vec[n//2:-n-n//2]) / d

def rollavg_roll(a,n):
    'Numpy array rolling'
    assert n%2==1
    N = len(a)
    rolling_idx = np.mod((N-1)*np.arange(n)[:,None] + np.arange(N), N)
    return a[rolling_idx].mean(axis=0)[n-1:] 

def rollavg_roll_edges(a,n):
    # see /programming/42101082/fast-numpy-roll
    'Numpy array rolling, edge handling'
    assert n%2==1
    a = np.pad(a,(0,n-1-n//2), 'constant')*np.ones(n)[:,None]
    m = a.shape[1]
    idx = np.mod((m-1)*np.arange(n)[:,None] + np.arange(m), m) # Rolling index
    out = a[np.arange(-n//2,n//2)[:,None], idx]
    d = np.hstack((np.arange(1,n),np.ones(m-2*n+1+n//2)*n,np.arange(n,n//2,-1)))
    return (out.sum(axis=0)/d)[n//2:]

def rollavg_pandas(a,n):
    'Pandas rolling average'
    return pd.DataFrame(a).rolling(n, center=True, min_periods=1).mean().to_numpy()

def rollavg_bottlneck(a,n):
    'bottleneck.move_mean'
    return bn.move_mean(a, window=n, min_count=1)

N = 10**6
a = np.random.rand(N)
functions = [rollavg_direct, rollavg_comprehension, rollavg_convolve, 
        rollavg_convolve_edges, rollavg_cumsum, rollavg_cumsum_edges, 
        rollavg_pandas, rollavg_bottlneck, rollavg_roll, rollavg_roll_edges]

print('Small window (n=3)')
%load_ext memory_profiler
for f in functions : 
    print('\n'+f.__doc__+ ' : ')
    %timeit b=f(a,3)

print('\nLarge window (n=1001)')
for f in functions[0:-2] : 
    print('\n'+f.__doc__+ ' : ')
    %timeit b=f(a,1001)

print('\nMemory\n')
print('Small window (n=3)')
N = 10**7
a = np.random.rand(N)
%load_ext memory_profiler
for f in functions[2:] : 
    print('\n'+f.__doc__+ ' : ')
    %memit b=f(a,3)

print('\nLarge window (n=1001)')
for f in functions[2:-2] : 
    print('\n'+f.__doc__+ ' : ')
    %memit b=f(a,1001)

Tempo, janela pequena (n = 3)

Direct "for" loop : 

4.14 s ± 23.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

List comprehension : 
3.96 s ± 27.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

scipy.convolve : 
1.07 ms ± 26.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

scipy.convolve, edge handling : 
4.68 ms ± 9.69 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

numpy.cumsum : 
5.31 ms ± 5.11 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

numpy.cumsum, edge handling : 
8.52 ms ± 11.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Pandas rolling average : 
9.85 ms ± 9.63 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

bottleneck.move_mean : 
1.3 ms ± 12.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Numpy array rolling : 
31.3 ms ± 91.9 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Numpy array rolling, edge handling : 
61.1 ms ± 55.9 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Tempo, janela grande (n = 1001)

Direct "for" loop : 
4.67 s ± 34 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

List comprehension : 
4.46 s ± 14.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

scipy.convolve : 
103 ms ± 165 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

scipy.convolve, edge handling : 
272 ms ± 1.23 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

numpy.cumsum : 
5.19 ms ± 12.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

numpy.cumsum, edge handling : 
8.7 ms ± 11.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Pandas rolling average : 
9.67 ms ± 199 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

bottleneck.move_mean : 
1.31 ms ± 15.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Memória, janela pequena (n = 3)

The memory_profiler extension is already loaded. To reload it, use:
  %reload_ext memory_profiler

scipy.convolve : 
peak memory: 362.66 MiB, increment: 73.61 MiB

scipy.convolve, edge handling : 
peak memory: 510.24 MiB, increment: 221.19 MiB

numpy.cumsum : 
peak memory: 441.81 MiB, increment: 152.76 MiB

numpy.cumsum, edge handling : 
peak memory: 518.14 MiB, increment: 228.84 MiB

Pandas rolling average : 
peak memory: 449.34 MiB, increment: 160.02 MiB

bottleneck.move_mean : 
peak memory: 374.17 MiB, increment: 75.54 MiB

Numpy array rolling : 
peak memory: 661.29 MiB, increment: 362.65 MiB

Numpy array rolling, edge handling : 
peak memory: 1111.25 MiB, increment: 812.61 MiB

Memória, janela grande (n = 1001)

scipy.convolve : 
peak memory: 370.62 MiB, increment: 71.83 MiB

scipy.convolve, edge handling : 
peak memory: 521.98 MiB, increment: 223.18 MiB

numpy.cumsum : 
peak memory: 451.32 MiB, increment: 152.52 MiB

numpy.cumsum, edge handling : 
peak memory: 527.51 MiB, increment: 228.71 MiB

Pandas rolling average : 
peak memory: 451.25 MiB, increment: 152.50 MiB

bottleneck.move_mean : 
peak memory: 374.64 MiB, increment: 75.85 MiB
argentum2f
fonte
11

Esta resposta usando Pandas foi adaptada acima, pois rolling_meannão faz mais parte do Pandas

# the recommended syntax to import pandas
import pandas as pd
import numpy as np

# prepare some fake data:
# the date-time indices:
t = pd.date_range('1/1/2010', '12/31/2012', freq='D')

# the data:
x = np.arange(0, t.shape[0])

# combine the data & index into a Pandas 'Series' object
D = pd.Series(x, t)

Agora, basta chamar a função rollingno dataframe com um tamanho de janela, que no meu exemplo abaixo é de 10 dias.

d_mva10 = D.rolling(10).mean()

# d_mva is the same size as the original Series
# though obviously the first w values are NaN where w is the window size
d_mva10[:11]

2010-01-01    NaN
2010-01-02    NaN
2010-01-03    NaN
2010-01-04    NaN
2010-01-05    NaN
2010-01-06    NaN
2010-01-07    NaN
2010-01-08    NaN
2010-01-09    NaN
2010-01-10    4.5
2010-01-11    5.5
Freq: D, dtype: float64
Vladtn
fonte
5

Acho que isso pode ser facilmente resolvido usando o gargalo

Veja o exemplo básico abaixo:

import numpy as np
import bottleneck as bn

a = np.random.randint(4, 1000, size=(5, 7))
mm = bn.move_mean(a, window=2, min_count=1)

Isso dá a média de movimento ao longo de cada eixo.

  • "mm" é a média móvel para "a".

  • "janela" é o número máximo de entradas a considerar para a média móvel.

  • "min_count" é o número mínimo de entradas a considerar para a média móvel (por exemplo, para o primeiro elemento ou se a matriz tiver valores nan).

A parte boa é que o gargalo ajuda a lidar com os valores nan e também é muito eficiente.

Anthony Anyanwu
fonte
2

No caso de você querer cuidar das condições de borda com cuidado ( calcular a média apenas dos elementos disponíveis nas bordas ), a função a seguir fará o truque.

import numpy as np

def running_mean(x, N):
    out = np.zeros_like(x, dtype=np.float64)
    dim_len = x.shape[0]
    for i in range(dim_len):
        if N%2 == 0:
            a, b = i - (N-1)//2, i + (N-1)//2 + 2
        else:
            a, b = i - (N-1)//2, i + (N-1)//2 + 1

        #cap indices to min and max indices
        a = max(0, a)
        b = min(dim_len, b)
        out[i] = np.mean(x[a:b])
    return out

>>> running_mean(np.array([1,2,3,4]), 2)
array([1.5, 2.5, 3.5, 4. ])

>>> running_mean(np.array([1,2,3,4]), 3)
array([1.5, 2. , 3. , 3.5])
Peixiang Zhong
fonte
1
for i in range(len(Data)):
    Data[i, 1] = Data[i-lookback:i, 0].sum() / lookback

Experimente este pedaço de código. Acho que é mais simples e faz o trabalho. lookback é a janela da média móvel.

No Data[i-lookback:i, 0].sum()coloquei 0para me referir à primeira coluna do conjunto de dados, mas você pode colocar qualquer coluna que desejar, caso tenha mais de uma coluna.

Sofien Kaabar
fonte
0

Na verdade, eu queria um comportamento ligeiramente diferente da resposta aceita. Eu estava construindo um extrator de recurso de média móvel para um sklearnpipeline, então exigi que a saída da média móvel tivesse a mesma dimensão da entrada. O que eu quero é que a média móvel assuma que a série permanece constante, ou seja, uma média móvel de [1,2,3,4,5]com a janela 2 daria [1.5,2.5,3.5,4.5,5.0].

Para vetores de coluna (meu caso de uso), obtemos

def moving_average_col(X, n):
  z2 = np.cumsum(np.pad(X, ((n,0),(0,0)), 'constant', constant_values=0), axis=0)
  z1 = np.cumsum(np.pad(X, ((0,n),(0,0)), 'constant', constant_values=X[-1]), axis=0)
  return (z1-z2)[(n-1):-1]/n

E para matrizes

def moving_average_array(X, n):
  z2 = np.cumsum(np.pad(X, (n,0), 'constant', constant_values=0))
  z1 = np.cumsum(np.pad(X, (0,n), 'constant', constant_values=X[-1]))
  return (z1-z2)[(n-1):-1]/n

Obviamente, não é necessário assumir valores constantes para o preenchimento, mas isso deve ser adequado na maioria dos casos.

cbartondock
fonte
0

talib contém uma ferramenta de média móvel simples, bem como outras ferramentas de cálculo de média semelhantes (ou seja, média móvel exponencial). Abaixo, compara o método com algumas das outras soluções.


%timeit pd.Series(np.arange(100000)).rolling(3).mean()
2.53 ms ± 40.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit talib.SMA(real = np.arange(100000.), timeperiod = 3)
348 µs ± 3.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit moving_average(np.arange(100000))
638 µs ± 45.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Uma ressalva é que o real deve ter elementos de dtype = float. Caso contrário, o seguinte erro é gerado

Exceção: o real não é duplo

Josmoor98
fonte
0

Aqui está uma implementação rápida usando numba (lembre-se dos tipos). Observe que ele contém nans onde alterado.

import numpy as np
import numba as nb

@nb.jit(nb.float64[:](nb.float64[:],nb.int64),
        fastmath=True,nopython=True)
def moving_average( array, window ):    
    ret = np.cumsum(array)
    ret[window:] = ret[window:] - ret[:-window]
    ma = ret[window - 1:] / window
    n = np.empty(window-1); n.fill(np.nan)
    return np.concatenate((n.ravel(), ma.ravel())) 
Mott The Tuple
fonte
Isso retorna nans no início.
Adam Erickson
0

média móvel

  • inverta o array em i e simplesmente tome a média de i para n.

  • use a compreensão de lista para gerar mini arrays dinamicamente.

x = np.random.randint(10, size=20)

def moving_average(arr, n):
    return [ (arr[:i+1][::-1][:n]).mean() for i, ele in enumerate(arr) ]
n = 5

moving_average(x, n)
Inivri
fonte
0

Eu uso a solução da resposta aceita , ligeiramente modificada para ter o mesmo comprimento de saída como entrada, ou pandasa versão mencionada em um comentário de outra resposta. Resumo ambos aqui com um exemplo reproduzível para referência futura:

import numpy as np
import pandas as pd

def moving_average(a, n):
    ret = np.cumsum(a, dtype=float)
    ret[n:] = ret[n:] - ret[:-n]
    return ret / n

def moving_average_centered(a, n):
    return pd.Series(a).rolling(window=n, center=True).mean().to_numpy()

A = [0, 0, 1, 2, 4, 5, 4]
print(moving_average(A, 3))    
# [0.         0.         0.33333333 1.         2.33333333 3.66666667 4.33333333]
print(moving_average_centered(A, 3))
# [nan        0.33333333 1.         2.33333333 3.66666667 4.33333333 nan       ]
Basj
fonte
0

Ao comparar a solução abaixo com aquela que usa cumsum de numpy, essa leva quase metade do tempo . Isso ocorre porque não é necessário percorrer todo o array para fazer o cumsum e depois fazer toda a subtração. Além disso, o cumsum pode ser " perigoso " se a matriz for enorme e o número for enorme ( possível estouro ). Claro, também aqui o perigo existe, mas pelo menos são somados apenas os números essenciais.

def moving_average(array_numbers, n):
    if n > len(array_numbers):
      return []
    temp_sum = sum(array_numbers[:n])
    averages = [temp_sum / float(n)]
    for first_index, item in enumerate(array_numbers[n:]):
        temp_sum += item - array_numbers[first_index]
        averages.append(temp_sum / float(n))
    return averages
Vittorio Carmignani
fonte