web-dev-qa-db-fra.com

Comment calculer la moyenne mobile à l'aide de NumPy?

Il semble n'y avoir aucune fonction qui calcule simplement la moyenne mobile sur numpy/scipy, conduisant à solutions compliquées .

Ma question est double:

  • Quel est le moyen le plus simple d'implémenter (correctement) une moyenne mobile avec numpy?
  • Puisque cela semble non trivial et sujet aux erreurs, y a-t-il une bonne raison de ne pas avoir les piles incluses dans ce cas?
67
goncalopp

Si vous voulez juste une moyenne mobile non pondérée simple, vous pouvez facilement la mettre en œuvre avec np.cumsum, qui peut être est plus rapide que les méthodes basées sur FFT:

EDITCorrigé une indexation erronée décochée par Bean dans le code. MODIFIER

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

Donc, je suppose que la réponse est la suivante: il est vraiment facile à mettre en œuvre et peut-être que numpy est déjà un peu gonflé de fonctionnalités spécialisées.

122
Jaime

L'absence d'une fonction spécifique à un domaine spécifique de NumPy est peut-être due à la discipline de son équipe principale et à sa fidélité à la directive principale de NumPy: fournit un type de tableau à N dimensions, ainsi qu'à des fonctions de création et d'indexation de ces tableaux. Comme de nombreux objectifs fondamentaux, celui-ci n'est pas petit et NumPy le fait avec brio.

Le (beaucoup) plus grand SciPy contient une collection beaucoup plus grande de bibliothèques spécifiques à un domaine (appelées sous-paquetages de SciPy devs) - par exemple, l'optimisation numérique (optimise), traitement du signal (signal) et calcul intégral (intègre).

Mon hypothèse est que la fonction que vous recherchez est dans au moins un des sous-packages SciPy (scipy.signal peut-être); Cependant, je regarderais d’abord dans la collection de sciky scikits, identifierais le (s) scikit (s) pertinent (s) et chercherais la fonction qui nous intéresse ici.

Les scikits sont des packages développés indépendamment, basés sur NumPy/SciPy et dirigés vers une discipline technique particulière (par exemple, scikits-image, scikits-learn, etc.) étaient (en particulier l’impressionnant OpenOpt pour l’optimisation numérique) des projets bien considérés et bien développés bien avant de choisir de résider sous la rubrique relativement nouvelle scikits. La page d'accueil de Scikits ci-dessus répertorie une trentaine de ces scikits, bien qu'au moins plusieurs d'entre eux ne soient plus en développement actif. 

Suivre ce conseil vous mènerait à scikits-timeseries} _; cependant, ce paquet n'est plus en développement actif; En effet, Pandas est devenu, autant que je sache, la bibliothèque de séries chronologiques basée sur de facto _ {NumPy-.

Pandas a plusieurs fonctions qui peuvent être utilisées pour calculer une moyenne mobile; le plus simple d'entre eux est probablement rolling_mean, que vous utilisez comme ceci:

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

Appelez maintenant la fonction rolling_mean en passant l'objet Series et un taille de la fenêtre, qui dans mon exemple ci-dessous est 10 jours.

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

vérifier le bon fonctionnement - par exemple, comparer les valeurs 10 à 15 de la série d'origine par rapport à la nouvelle série lissée avec la moyenne mobile

>>> 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 fonction rolling_mean, avec environ une douzaine d'autres fonctions, sont regroupées de manière informelle dans la documentation Pandas sous la rubrique fenêtre mobile fonctions; un second groupe de fonctions apparentées dans les pandas est appelé fonction pondérée de manière exponentielle (par exemple, ewma, qui calcule la moyenne pondérée en mouvement de manière exponentielle). Le fait que ce second groupe ne soit pas inclus dans le premier (fenêtre mobile fonctions) est peut-être dû au fait que les transformations à pondération exponentielle ne reposent pas sur une fenêtre de longueur fixe.

68
doug

Un moyen simple d'y parvenir consiste à utiliser np.convolve . L'idée est de tirer parti de la façon dont la convolution discrete est calculée et de l'utiliser pour renvoyer une moyenne glissante. Cela peut être fait en convolution avec une séquence de np.ones d'une longueur égale à la longueur de sliding window que nous voulons.

Pour ce faire, nous pourrions définir la fonction suivante:

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

Cette fonction prend la convolution du signal x et une séquence de ceux de longueur w. Notez que la variable mode choisie est valid, de sorte que le produit de convolution n'est donné que pour les points où les signaux se chevauchent complètement.


Cas d'utilisation

Regardons quelques exemples:

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

Pour une moyenne mobile avec une fenêtre de longueur 2 nous aurions:

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

Et pour une fenêtre de longueur 4:

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

Détails 

Voyons plus en détail la façon dont la convolution discrète est calculée . La fonction suivante vise à reproduire la façon dont np.convolve calcule les valeurs de sortie:

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

Ce qui, pour le même exemple ci-dessus donnerait aussi:

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

Donc, ce qui est fait à chaque étape est de prendre le produit intérieur entre le tableau des uns et le courant  la fenêtre. Dans ce cas, la multiplication par np.ones(w) est superflue étant donné que nous prenons directement la sum de la séquence.

Ci-dessous est un exemple de la façon dont les premières sorties sont calculées pour que ce soit un peu plus clair. Supposons que nous voulions une fenêtre de w=4:

[1,1,1,1]
[1,2,3,4,5,6,7,8...]
= (1*1 + 1*2 + 1*3 + 1*4) / w = 2.5

Et la sortie suivante serait calculée comme suit:

  [1,1,1,1]
[1,2,3,4,5,6,7,8...]
= (1*2 + 1*3 + 1*4 + 1*5) / w = 3.5

Et ainsi de suite, cédant comme mentionné précédemment un  moyenne mobile  de x.

2
yatu

Cette réponse utilisant Pandas est adaptée ci-dessus, car rolling_mean ne fait plus partie des 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)

Maintenant, appelez simplement la fonction rolling sur le cadre de données avec une taille de fenêtre, qui dans mon exemple ci-dessous est de 10 jours.

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

Je pense que cela peut être facilement résolu en utilisant étranglement

Voir échantillon de base ci-dessous:

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)

Cela donne la moyenne de déplacement le long de chaque axe.

  • "mm" est le moyen mobile pour "a". 

  • "fenêtre" est le nombre maximum d'entrées à prendre en compte pour la moyenne mobile. 

  • "min_count" est le nombre minimal d'entrées à prendre en compte pour le déplacement de la moyenne (par exemple, pour le premier élément ou si le tableau a des valeurs nan).

La bonne partie est Bottleneck aide à traiter avec des valeurs nan et est également très efficace.

1
Anthony Anyanwu

Si vous voulez prendre soin des conditions de bord avec précaution ( calculer la moyenne uniquement à partir des éléments disponibles aux bords ), la fonction suivante fera l'affaire. 

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])
0
Peixiang Zhong

Je voulais en fait un comportement légèrement différent de la réponse acceptée. Je construisais un extracteur d'entités de moyenne mobile pour un pipeline sklearn. J'avais donc besoin que la sortie de la moyenne mobile ait la même dimension que l'entrée. Ce que je veux, c'est que la moyenne mobile suppose que la série reste constante, c'est-à-dire qu'une moyenne mobile de [1,2,3,4,5] avec la fenêtre 2 donnerait [1.5,2.5,3.5,4.5,5.0].

Pour les vecteurs de colonne (mon cas d'utilisation) nous obtenons

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

Et pour les tableaux

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

Bien sûr, il n’est pas nécessaire d’assumer des valeurs constantes pour le remplissage, mais cela devrait suffire dans la plupart des cas.

0
cbartondock