¿Cómo calcular la media móvil usando NumPy?

109

Parece que no hay una función que simplemente calcule el promedio móvil en numpy / scipy, lo que lleva a soluciones complicadas .

Mi pregunta es doble:

  • ¿Cuál es la forma más fácil de implementar (correctamente) una media móvil con numpy?
  • Dado que esto parece no trivial y propenso a errores, ¿hay alguna buena razón para no incluir las baterías en este caso?
Goncalopp
fuente
19
¡La solución de convolución no me parece tan complicada!
wim
4
¿No es una media móvil solo un filtro de paso bajo (es decir, 'desenfoque')?
Estoy
@mmgp Supongo que esperaba estar equivocado, o que había una buena razón obvia.
goncalopp
3
@wim Fue a medias como un juego de palabras. Pero el mero hecho de que exista la pregunta significa que no es sencillo crear una media móvil a partir de numpy.convolute.
goncalopp
3
Posible duplicado de la media móvil o media
móvil

Respuestas:

165

Si lo que desea es un simple no ponderado promedio móvil, se puede implementar fácilmente con np.cumsum, que puede ser es métodos más rápido que la FFT en base:

EDITAR Se corrigió una indexación incorrecta de uno en uno detectada por Bean en el 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])

Así que supongo que la respuesta es: es realmente fácil de implementar, y tal vez numpy ya esté un poco hinchado con funcionalidad especializada.

Jaime
fuente
10
Este código es incorrecto. por ejemplo, promedio_móvil ([1, 2, 5, 10], n = 2) da [1., 3.5, 8.5]. Incluso el caso de prueba del respondedor para un promedio móvil de valores de 0 a 19 es incorrecto, afirmando que el promedio de 0, 1 y 2 es 0.5. ¿Cómo consiguió 6 votos a favor?
JeremyKun
2
Gracias por la comprobación de errores, ahora parece estar funcionando bien. En cuanto a los votos a favor, supongo que la idea general detrás de la respuesta pesó más que un error de uno en uno en la implementación, pero quién sabe.
Jaime
2
He encontrado el problema. ret[n:] -= ret[:-n]NO ES LO MISMO que ret[n:] = ret[n:] - ret[:-n]. He arreglado el código en esta respuesta. Editar: No, alguien más se me adelantó.
Timmmm
7
@Timmmm lo hice, ese era de hecho el problema. El principio general detrás de esta respuesta se usa ampliamente en el procesamiento de imágenes (lo llaman tablas de áreas sumadas), por lo que el problema tenía que estar en la implementación. Un buen ejemplo de estar atrapado por la optimización prematura, ya que recuerdo haber hecho la operación en el lugar "porque será más eficiente". En el lado positivo, probablemente produjo la respuesta incorrecta más rápido ...
Jaime
43
Mmmm, parece que esta función "fácil de implementar" es bastante fácil de equivocarse y ha fomentado una buena discusión sobre la eficiencia de la memoria. Me alegra tener hinchazón si eso significa saber que algo se ha hecho bien.
Richard
81

La falta de NumPy de una función específica de dominio particular se debe quizás a la disciplina y fidelidad del Core Team a la directiva principal de NumPy: proporcionar un tipo de matriz N-dimensional , así como funciones para crear e indexar esas matrices. Como muchos objetivos fundamentales, este no es pequeño y NumPy lo hace de manera brillante.

El SciPy (mucho) más grande contiene una colección mucho mayor de bibliotecas específicas de dominio (llamadas subpaquetes por los desarrolladores de SciPy), por ejemplo, optimización numérica ( optimizar ), procesamiento de señales ( señal ) y cálculo integral ( integrar ).

Supongo que la función que busca está en al menos uno de los subpaquetes de SciPy ( quizás scipy.signal ); sin embargo, buscaría primero en la colección de scikits de ciencia ficción , identificaría los scikit relevantes y buscaría la función de interés allí.

Scikits son paquetes desarrollados de forma independiente basados ​​en NumPy / SciPy y dirigidos a una disciplina técnica en particular (por ejemplo, scikits-image , scikits-learn , etc.). Varios de ellos (en particular, el impresionante OpenOpt para optimización numérica) fueron muy apreciados, proyectos maduros mucho antes de elegir residir bajo la rúbrica de scikits relativamente nueva . La página de inicio de Scikits que le gustó arriba enumera alrededor de 30 de esos scikits , aunque al menos varios de ellos ya no están en desarrollo activo.

Seguir este consejo lo llevará a scikits-timeseries ; sin embargo, ese paquete ya no se encuentra en desarrollo activo; En efecto, Pandas se ha convertido, AFAIK, el de facto NumPy biblioteca de series de tiempo basada en .

Pandas tiene varias funciones que pueden usarse para calcular un promedio móvil ; el más simple de estos es probablemente rolling_mean , que usa así:

>>> # 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)

Ahora, simplemente llame a la función rolling_mean pasando el objeto Serie y un tamaño de ventana , que en mi ejemplo a continuación es de 10 días .

>>> 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

verificar que funcionó, por ejemplo, comparar los valores 10-15 en la serie original con la nueva Serie suavizada con media móvil

>>> 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

La función rolling_mean, junto con alrededor de una docena de otras funciones se agrupan informalmente en la documentación de Pandas bajo la rúbrica funciones de ventana móvil ; un segundo grupo de funciones relacionado en Pandas se conoce como funciones ponderadas exponencialmente (por ejemplo, ewma , que calcula el promedio ponderado móvil exponencial). El hecho de que este segundo grupo no esté incluido en el primero ( funciones de ventana móvil ) se debe quizás a que las transformaciones ponderadas exponencialmente no dependen de una ventana de longitud fija

doug
fuente
6
Pandas tiene una sólida línea de funciones de ventana móvil. Pero me parece que es demasiado para un promedio móvil simple.
Jaime
6
Bueno, dudo que calcular una media móvil sea un requisito aislado para el OP o para cualquier otra persona. Si necesita calcular un promedio móvil, es casi seguro que tenga una serie de tiempo, lo que significa que necesita una estructura de datos que le permita ajustar un índice de fecha y hora a sus datos y esa es la 'sobrecarga' a la que se refiere.
doug
2
En primer lugar, gracias por tomarse el tiempo de escribir esta respuesta tan informativa. De hecho, no veo un uso para una media móvil que no involucre una serie de tiempo. Pero eso no significa que uno deba ajustarlo a una fecha y hora, o incluso a un intervalo de muestreo en particular (puede ser desconocido)
goncalopp
3
Solo quería agregar que la función de promedio móvil se ha extraído a la biblioteca Bottleneck si los pandas parecen demasiado pesados ​​como dependencia.
robochat
4
'rolling_mean' ya no es parte de pf pandas, por favor vea responder usando 'rolling' en su lugar
Vladtn
60

Una forma sencilla de lograr esto es usando np.convolve. La idea detrás de esto es aprovechar la forma en que se calcula la convolución discreta y usarla para devolver una media móvil . Esto se puede hacer convolucionando con una secuencia denp.ones de una longitud igual a la longitud de la ventana deslizante que queremos.

Para ello podríamos definir la siguiente función:

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

Esta función tomará la convolución de la secuencia xy una secuencia de unos de longitud w. Tenga en cuenta que lo elegido modees validpara que el producto de convolución solo se proporcione para los puntos donde las secuencias se superponen por completo.


Algunos ejemplos:

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

Para una media móvil con una ventana de longitud 2tendríamos:

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

Y para una ventana de largo 4:

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

¿Cómo convolvefunciona?

Echemos un vistazo más en profundidad a la forma en que se calcula la convolución discreta. La siguiente función tiene como objetivo replicar la formanp.convolve se calculan los valores de salida:

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

Lo cual, para el mismo ejemplo anterior, también produciría:

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

Entonces, lo que se está haciendo en cada paso es tomar el producto interno entre la matriz de unos y la ventana actual . En este caso la multiplicación por np.ones(w)es superflua dado que estamos tomando directamente lasum de la secuencia.

A continuación se muestra un ejemplo de cómo se calculan las primeras salidas para que sea un poco más claro. Supongamos que queremos una ventana 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

Y la siguiente salida se calcularía 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

Y así sucesivamente, devolviendo un promedio móvil de la secuencia una vez que se hayan realizado todas las superposiciones.

yatu
fuente
¡Esta es una buena idea! Es más rápido que la respuesta de @ Jaime para n pequeña, pero se vuelve más lenta para n más grande.
Felipe Gerard
¡Gracias @FelipeGerard! Si como se ha señalado en los comentarios, si bien este enfoque puede tal vez no sea tan eficiente como algunas otras soluciones numpy, la OMI es bueno tener como alternativa para futuros visitantes, dada su sencillez y concisión
Yatu
A veces es útil tener una matriz de salida del mismo tamaño que la entrada. Para ello, mode='valid'se puede reemplazar con 'same'. Solo en este caso, los puntos de borde gravitarán hacia cero.
Ilia Barahovski
15

Aquí hay una variedad de formas de hacer esto, junto con algunos puntos de referencia. Los mejores métodos son las versiones que utilizan código optimizado de otras bibliotecas. El bottleneck.move_meanmétodo probablemente sea el mejor en todos los aspectos. El scipy.convolveenfoque también es muy rápido, extensible y sintácticamente y conceptualmente simple, pero no se escala bien para valores de ventana muy grandes. El numpy.cumsummétodo es bueno si necesitas un puronumpy enfoque .

Nota: algunos de estos (p bottleneck.move_mean. Ej. ) No están centrados y cambiarán sus datos.

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)

Tiempo, ventana pequeña (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)

Tiempo, ventana 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)

Memoria, ventana pequeña (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

Memoria, ventana 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
fuente
11

Esta respuesta que usa Pandas está adaptada de arriba, ya que rolling_meanya no es parte de 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)

Ahora, simplemente llame a la función rollingen el marco de datos con un tamaño de ventana, que en mi ejemplo a continuación es de 10 días.

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
fuente
5

Siento que esto se puede resolver fácilmente usando el cuello de botella

Vea la muestra básica a continuación:

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)

Esto da media de movimiento a lo largo de cada eje.

  • "mm" es la media móvil de "a".

  • "ventana" es el número máximo de entradas a considerar para la media móvil.

  • "min_count" es el número mínimo de entradas a considerar para la media móvil (por ejemplo, para el primer elemento o si la matriz tiene valores nan).

Lo bueno es que Bottleneck ayuda a lidiar con los valores nan y también es muy eficiente.

Anthony Anyanwu
fuente
2

En caso de que desee cuidar las condiciones de los bordes con cuidado ( calcule la media solo a partir de los elementos disponibles en los bordes ), la siguiente función funcionará.

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
fuente
1
for i in range(len(Data)):
    Data[i, 1] = Data[i-lookback:i, 0].sum() / lookback

Prueba este fragmento de código. Creo que es más simple y funciona. retroceso es la ventana de la media móvil.

En el Data[i-lookback:i, 0].sum()he puesto 0para referirse a la primera columna del conjunto de datos pero puedes poner cualquier columna que quieras en caso de que tengas más de una columna.

Sofien Kaabar
fuente
0

De hecho, quería un comportamiento ligeramente diferente a la respuesta aceptada. Estaba construyendo un extractor de características de media móvil para una sklearntubería, por lo que necesitaba que la salida de la media móvil tuviera la misma dimensión que la entrada. Lo que quiero es que la media móvil asuma que la serie permanece constante, es decir, una media móvil de [1,2,3,4,5]con la ventana 2 daría[1.5,2.5,3.5,4.5,5.0] .

Para los vectores de columna (mi caso de uso) obtenemos

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

Y para matrices

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

Por supuesto, no es necesario asumir valores constantes para el relleno, pero hacerlo debería ser adecuado en la mayoría de los casos.

cbartondock
fuente
0

talib contiene una herramienta de promedio móvil simple, así como otras herramientas de promedio similares (es decir, promedio móvil exponencial). A continuación, se compara el método con algunas de las otras soluciones.


%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)

Una advertencia es que lo real debe tener elementos de dtype = float. De lo contrario, se genera el siguiente error

Excepción: real no es doble

Josmoor98
fuente
0

Aquí hay una implementación rápida usando numba (tenga en cuenta los tipos). Tenga en cuenta que contiene nans donde se desplazó.

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 la tupla
fuente
Esto devuelve nans al principio.
Adam Erickson
0

media móvil

  • invierta la matriz en i, y simplemente tome la media de ia n.

  • use la comprensión de listas para generar mini matrices sobre la marcha.

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
fuente
0

Utilizo la solución de la respuesta aceptada , ligeramente modificada para tener la misma longitud para la salida que la entrada, o pandasla versión como se menciona en un comentario de otra respuesta. Resumo ambos aquí con un ejemplo reproducible para referencia 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
fuente
0

Al comparar la solución a continuación con la que usa cumsum of numpy, esta toma casi la mitad del tiempo . Esto se debe a que no necesita pasar por toda la matriz para hacer el cumsum y luego hacer toda la resta. Además, el cumsum puede ser " peligroso " si la matriz es enorme y el número es enorme ( posible desbordamiento ). Por supuesto, también aquí existe el peligro, pero al menos se suman solo los números esenciales.

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
fuente