Вычислить скользящую / скользящую среднюю в C++

Я знаю, что это достижимо с boost согласно:

используя boost:: аккумуляторы, как я могу сбросить размер окна качения, сохраняет ли он дополнительную историю?

но я действительно хотел бы избежать использования boost. Я googled и не нашел подходящих или читаемых примеров.

в основном я хочу отслеживать скользящую среднюю текущего потока потока чисел с плавающей запятой, используя самые последние 1000 чисел в качестве данных образец.

каков самый простой способ достичь этого?


я экспериментировал с использованием кругового массива, экспоненциальной скользящей средней и более простой скользящей средней и обнаружил, что результаты кругового массива лучше всего соответствуют моим потребностям.

9 ответов


вам просто нужен круговой массив из 1000 элементов, где вы добавляете элемент к предыдущему элементу и сохраняете его... Это становится возрастающей суммой, где вы всегда можете получить сумму между любыми двумя парами элементов и разделить на количество элементов между ними, чтобы получить среднее значение.


Если ваши потребности просты, вы можете просто попробовать использовать экспоненциальную скользящую среднюю.

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

проще говоря, вы делаете переменную аккумулятора, и когда ваш код смотрит на каждый образец, код обновляет аккумулятор новым значением. Вы выбираете постоянную "Альфа", которая находится между 0 и 1, и вычисляете это:

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

вам просто нужно найти значение "alpha", где влияние, Котор дали образца только продолжает Для около 1000 образцов.


вы можете приблизить скользящее среднее, применяя взвешенное среднее на входном потоке.

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

таким образом, вам не нужно поддерживать 1000 ведер. Однако это приближение, поэтому его значение не будет точно соответствовать истинной скользящей средней.

Edit: только что заметил сообщение @steveha. Это эквивалентно экспоненциальной скользящей средней, причем Альфа равна 1 / N (в этом случае я принимал N за 1000, чтобы имитировать 1000 ведер).


в основном я хочу отслеживать скользящую среднюю текущего потока потока чисел с плавающей запятой, используя самые последние 1000 чисел в качестве образца данных.

обратите внимание, что ниже обновляет total_ как элементы, добавленные / замененные, избегая дорогостоящего обхода O(N) для расчета суммы, необходимой для среднего - по требованию.

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 сделан другим параметром от T для поддержки, например с помощью long long когда на сумму 1000 longs, an int на chars, или a double в общей сумме floats.

это немного испорчено в этом num_samples_ мог пройти мимо INT_MAX - если вы заботитесь вы могли бы использовать unsigned long long, или использовать дополнительный член данных bool для записи, когда контейнер сначала заполняется во время циклического num_samples_ вокруг массива (лучше всего переименовать что-то безобидное, как "pos").


простой класс для расчета скользящего среднего, а также стандартного отклонения:

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

};

вы можете реализовать кольцевой буфер. Сделать массив из 1000 элементов, и некоторые поля для хранения начального и конечного индексов и общий размер. Затем просто сохраните последние 1000 элементов в кольцевом буфере и пересчитайте среднее значение по мере необходимости.


простая скользящая средняя для 10 элементов, используя список:

#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();
}

одним из способов может быть круговое хранение значений в буферном массиве. и вычислить среднее таким образом.

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

все это работает в цикле, где самое последнее значение является динамическим.


Я использую это довольно часто в жестких системах реального времени, которые имеют довольно безумные скорости обновления (50kilosamples/sec), в результате я обычно предварительно вычисляю скаляры.

вычислить скользящее среднее из N выборок: скаляр1 = 1 / N; scalar2 = 1 - scalar1; // или (1 - 1/п) затем:

Average = currentSample * scalar1 + Average * scalar2;

пример: скользящее среднее 10 элементов

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);
   }
 }

Примечание: это просто практическая реализация ответа дано стивехой выше. Иногда легче понять конкретный пример.