Calculer la moyenne mobile / mobile en C++

je sais que c'est réalisable avec élan par:

utiliser boost::accumulateurs, Comment puis-je réinitialiser la taille d'une fenêtre de roulement, est-ce qu'il garde l'histoire supplémentaire?

Mais je voudrais vraiment éviter d'utiliser boost. J'ai cherché sur Google et je n'ai trouvé aucun exemple adapté ou lisible.

fondamentalement, je veux suivre la moyenne mobile d'un flux continu d'un nombre de points flottants en utilisant le plus récent 1000 nombres comme échantillon de données.

Quelle est la façon la plus facile d'y parvenir?


j'ai expérimenté l'utilisation d'un réseau circulaire, d'une moyenne exponentielle mobile et d'une moyenne mobile plus simple et j'ai trouvé que les résultats du réseau circulaire convenaient le mieux à mes besoins.

35
demandé sur Community 2012-06-12 08:38:09

9 réponses

Vous avez simplement besoin d'un tableau circulaire de 1000 éléments, où vous ajoutez l'élément à l'élément précédent, et de les stocker... Il devient une somme croissante, où vous pouvez toujours obtenir la somme entre deux paires d'éléments, et diviser par le nombre d'éléments entre eux, pour produire la moyenne.

18
répondu Karthik Kumar Viswanathan 2012-06-12 04:50:22

Si vos besoins sont simples, vous pouvez simplement essayer d'utiliser une moyenne mobile exponentielle.

http://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average

pour faire simple, vous faites une variable d'accumulateur, et comme votre code regarde chaque échantillon, le code met à jour l'accumulateur avec la nouvelle valeur. Vous choisissez une constante "alpha" qui est entre 0 et 1, et calculer ceci:

accumulator = (alpha * new_value) + (1.0 - alpha) * accumulator

Vous avez juste besoin de trouver une valeur de "alpha", où l'effet d'un échantillon donné ne dure qu'environ 1000 échantillons.

Hmm, je ne suis pas vraiment sûr que ce soit approprié pour vous, maintenant que je l'ai mis ici. Le problème est que 1000 est une fenêtre assez longue pour une moyenne exponentielle Mobile; Je ne suis pas sûr qu'il y ait un alpha qui répartirait la moyenne sur les 1000 derniers nombres, sans sous-flux dans le calcul de la virgule flottante. Mais si vous vouliez une moyenne plus petite, comme 30 nombres ou alors, c'est une très moyen facile et rapide de le faire.

76
répondu steveha 2012-06-12 06:32:56

vous pouvez vous rapprocher d'une moyenne mobile en appliquant une moyenne pondérée sur votre flux d'entrée.

template <unsigned N>
double approxRollingAverage (double avg, double input) {
    avg -= avg/N;
    avg += input/N;
    return avg;
}

de Cette façon, vous n'avez pas besoin de maintenir 1000 seaux. Cependant, il s'agit d'une approximation, de sorte que sa valeur ne correspondra pas exactement à une vraie moyenne mobile.

Edit: je viens de remarquer le billet de @steveha. C'est l'équivalent de la moyenne exponentielle mobile, avec l'alpha étant 1 / N (je prenais n pour 1000 dans ce cas pour simuler 1000 seau.)

14
répondu jxh 2012-06-12 06:00:32

fondamentalement, je veux suivre la moyenne mobile d'un flux continu d'un nombre de virgule flottante en utilisant les 1000 nombres les plus récents comme un échantillon de données.

noter que le ci - dessous met à jour le total comme éléments ajoutés/remplacés, en évitant coûteux O(N) transversal pour calculer la somme - nécessaire pour la moyenne-sur demande.

template <typename T, typename Total, int N>
class Moving_Average
{
  public:
    Moving_Average()
      : num_samples_(0), total_(0)
    { }

    void operator()(T sample)
    {
        if (num_samples_ < N)
        {
            samples_[num_samples_++] = sample;
            total_ += sample;
        }
        else
        {
            T& oldest = samples_[num_samples_++ % N];
            total_ += sample - oldest;
            oldest = sample;
        }
    }

    operator double() const { return total_ / std::min(num_samples_, N); }

  private:
    T samples_[N];
    int num_samples_;
    Total total_;
};

Total est un paramètre différent de T pour supporter par exemple l'utilisation d'un long long pour un total de 1000 long s, d'un int pour char s, ou d'un double pour un total de float s.

c'est un peu vicié dans le fait que num_samples_ pourrait passer INT_MAX - si vous vous souciez vous pourriez utiliser un unsigned long long , ou utiliser un membre de données supplémentaire bool pour enregistrer quand le conteneur est rempli tout d'abord en faisant tourner num_samples_ autour du tableau (mieux alors renommé quelque chose d'inoffensif comme " pos ").

11
répondu Tony Delroy 2014-06-08 14:28:31

classe Simple pour calculer la moyenne au roulement et l'écart-type au roulement:

#define _stdev(cnt, sum, ssq) sqrt((((double)(cnt))*ssq-pow((double)(sum),2)) / ((double)(cnt)*((double)(cnt)-1)))

class moving_average {
private:
    boost::circular_buffer<int> *q;
    double sum;
    double ssq;
public:
    moving_average(int n)  {
        sum=0;
        ssq=0;
        q = new boost::circular_buffer<int>(n);
    }
    ~moving_average() {
        delete q;
    }
    void push(double v) {
        if (q->size() == q->capacity()) {
            double t=q->front();
            sum-=t;
            ssq-=t*t;
            q->pop_front();
        }
        q->push_back(v);
        sum+=v;
        ssq+=v*v;
    }
    double size() {
        return q->size();
    }
    double mean() {
        return sum/size();
    }
    double stdev() {
        return _stdev(size(), sum, ssq);
    }

};
3
répondu Erik Aronesty 2013-12-09 20:58:30

vous pouvez implémenter un ring buffer . Faites un tableau de 1000 éléments, et quelques champs pour stocker les index de début et de fin et la taille totale. Puis il suffit de stocker les 1000 derniers éléments dans le tampon annulaire, et de recalculer la moyenne si nécessaire.

1
répondu Tim 2012-06-12 04:50:58

une moyenne mobile simple pour 10 points, à l'aide d'une liste:

#include <list>

std::list<float> listDeltaMA;

float getDeltaMovingAverage(float delta)
{
    listDeltaMA.push_back(delta);
    if (listDeltaMA.size() > 10) listDeltaMA.pop_front();
    float sum = 0;
    for (std::list<float>::iterator p = listDeltaMA.begin(); p != listDeltaMA.end(); ++p)
        sum += (float)*p;
    return sum / listDeltaMA.size();
}
0
répondu Pedro Soares 2014-01-27 00:46:21

une façon peut être de stocker circulairement les valeurs dans le tableau buffer. et de calculer la moyenne de cette façon.

int j = (int) (counter % size);
buffer[j] = mostrecentvalue;
avg = (avg * size - buffer[j - 1 == -1 ? size - 1 : j - 1] + buffer[j]) / size;

counter++;

// buffer[j - 1 == -1 ? size - 1 : j - 1] is the oldest value stored

le tout fonctionne dans une boucle où la valeur la plus récente est dynamique.

0
répondu Nilesh Kumar Jha 2015-12-01 18:13:20

Je l'utilise assez souvent dans des systèmes durs en temps réel qui ont des taux de mise à jour assez insensés (50kilosamples/sec) en conséquence je calcule généralement les scalaires.

Pour calculer une moyenne mobile de N échantillons: scalar1 = 1/N; scalar2 = 1-scalar1; / / or (1-1 / N) puis:

Moyenne = currentSample*scalar1 + Moyenne*scalar2;

exemple: moyenne mobile de 10 éléments

double scalar1 = 1.0/10.0;  // 0.1
double scalar2 = 1.0 - scalar1; // 0.9
bool first_sample = true;
double average=0.0;
while(someCondition)
{
   double newSample = getSample();
   if(first_sample)
   {
    // everybody forgets the initial condition *sigh*
      average = newSample;
      first_sample = false;
   }
   else
   {
      average = (sample*scalar1) + (average*scalar2);
   }
 }

Note: Ceci est juste une mise en œuvre pratique de la réponse donnée par steveha ci-dessus. Parfois, il est plus facile de comprendre un exemple concret.

0
répondu baumann 2018-01-25 01:09:10