Вычислить скользящую / скользящую среднюю в C++
Я знаю, что это достижимо с 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 long
s, an int
на char
s, или a double
в общей сумме float
s.
это немного испорчено в этом 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);
}
}
Примечание: это просто практическая реализация ответа дано стивехой выше. Иногда легче понять конкретный пример.