Calculer la moyenne mobile / mobile en C++
je sais que c'est réalisable avec élan par:
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.
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.
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.
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.)
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
").
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);
}
};
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.
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();
}
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.
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.