Как рассчитать скользящее среднее без учета подсчета и итоговых данных?


119

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

Я придумал два алгоритма, но оба должны хранить счетчик:

  • новое среднее значение = ((старое количество * старые данные) + следующие данные) / следующее количество
  • новое среднее = старое среднее + (следующие данные - старое среднее) / следующий счет

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

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

Возможно ли это, или я просто ищу невозможное?


1
Обратите внимание, что числовое сохранение текущего общего и текущего счета является наиболее стабильным способом. В противном случае, для более высоких счетчиков следующий / (следующий счет) начнет не заполняться. Так что, если вы действительно беспокоитесь о потере точности, сохраняйте итоги!
AlexR

1
См. Википедию en.wikipedia.org/wiki/Moving_average
xmedeko

Ответы:


91

Вы можете просто сделать:

double approxRollingAverage (double avg, double new_sample) {

    avg -= avg / N;
    avg += new_sample / N;

    return avg;
}

Где Nколичество образцов, по которым вы хотите усреднить. Обратите внимание, что это приближение эквивалентно экспоненциальной скользящей средней. См .: Расчет скользящего / скользящего среднего в C ++


3
Разве вам не нужно добавлять 1 к N перед этой строкой? avg + = new_sample / N;
Damian

20
Это не совсем так. То, что описывает @Muis, является экспоненциально взвешенным скользящим средним, что иногда уместно, но не совсем то, что запрашивал OP. В качестве примера рассмотрим ожидаемое поведение, когда большинство точек находятся в диапазоне от 2 до 4, но одно значение превышает миллион. EWMA (здесь) еще довольно долго будет удерживать следы этого миллиона. Конечная свертка, как обозначено OP, потеряет ее сразу после N шагов. У него есть преимущество постоянного хранения.
jma

9
Это не скользящая средняя. То, что вы описываете, - это однополюсный фильтр, который создает экспоненциальные отклики на скачки сигнала. Скользящая средняя создает линейный отклик длиной N.
ruhig brauner

3
Помните, что это довольно далеко от общепринятого определения среднего. Если вы установите N = 5 и введете 5 5выборок, среднее значение будет 0,67.
Дэн Даскалеску 09

2
@DanDascalescu. Хотя вы правы в том, что на самом деле это не скользящее среднее, ваше заявленное значение на порядок меньше. При avgинициализации в 0вы получите 3.36через 5 5с, а 4.46через 10: cpp.sh/2ryql Для длинных средних это, безусловно, полезное приближение.
cincodenada

80
New average = old average * (n-1)/n + new value /n

Предполагается, что счетчик изменился только на одно значение. В случае его изменения на M значения:

new average = old average * (n-len(M))/n + (sum of values in M)/n).

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


Какая сумма новой стоимости? это чем-то отличается от "нового значения" в исходной формуле?
Михаил

@Mikhail во втором примере есть mновые значения, которые учитываются в новом среднем. Я считаю, что sum of new valueздесь подразумевается сумма mновых значений, используемых для вычисления нового среднего.
Патрик

10
Чуть эффективнее для первого: new_average = (old_average * (n-1) + new_value) / n- Убирает одну из перегородок.
Pixelstix

Как насчет среднего значения 3 элементов с 6,0,0,9?
Рошан Мехта

1
Когда я реализую это уравнение, значение или скользящее среднее всегда медленно увеличивается. Он никогда не падает - только вверх.
anon58192932 06

30

Из блога о выполнении выборочных расчетов дисперсии, где среднее также вычисляется с использованием метода Велфорда :

введите описание изображения здесь

Жаль, что мы не можем загружать изображения SVG.


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

На самом деле это ближе к @ Abdullah-Al-Ageel (по сути, коммутативной математике) в том смысле, что Муис не учитывает приращение N; Ссылка на формулу копирования и вставки: [Среднее при n] = [Сред. при n-1] + (x - [Сред. при n-1]) / n
drzaus

2
@Flip & drwaus: Разве решения Муиса и Абдуллы Аль-Аджила не совпадают? Это то же самое вычисление, только написанное по-другому. Для меня эти 3 ответа идентичны, этот более нагляден (жаль, что мы не можем использовать MathJax на SO).
user276648

23

Вот еще один ответ предложение комментарии о том , как MUIS , Абдуллы Аль-Ageel и Флип ответ «сек являются все математически то же самое , за исключением того, написаны по- разному.

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

Однако есть довольно большая разница

Это в MUIS 's N, Флип ' s k, и Абдулла аль-Ageel «s n. Абдулла Аль-Агил не совсем объясняет, что nдолжно быть, но Nи kотличается в этомN это « число выборок , где вы хотите , чтобы в среднем за » , а kявляется подсчет значений выборки. (Хотя я сомневаюсь в точности N определения количества образцов .)

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

Экспоненциально взвешенное скользящее среднее

Первоначально:

average = 0
counter = 0

Для каждого значения:

counter += 1
average = average + (value - average) / min(counter, FACTOR)

Разница в том min(counter, FACTOR) . Это то же самое, что сказать min(Flip's k, Muis's N).

FACTOR- константа, которая влияет на то, как быстро среднее значение «догоняет» последний тренд. Чем меньше число, тем быстрее. (Сейчас 1это уже не среднее значение, а просто последнее значение.)

Для этого ответа требуется счетчик хода counter. Если проблематично, его min(counter, FACTOR)можно заменить просто FACTOR, превратив его в ответ Муиса . Проблема с этим заключается в том, что на скользящую среднюю влияют всеaverage инициализировано. Если он был инициализирован0 , этот ноль может занять много времени, чтобы выйти из среднего значения.

Как это в итоге выглядит

Экспоненциальная скользящая средняя


3
Хорошо объяснено. Мне просто не хватает простого среднего на вашем графике, потому что это то, о чем спрашивал OP.
xmedeko

Может я что-то упускаю, но ты случайно имел в виду max(counter, FACTOR). min(counter, FACTOR)всегда будет возвращать FACTOR, верно?
WebWanderer 03

1
Я считаю, что суть в том min(counter, FACTOR), чтобы учесть период разминки. Без него, если ваш КОЭФФИЦИЕНТ (или N, или желаемое количество образцов) равен 1000, вам понадобится не менее 1000 образцов, прежде чем вы получите точный результат, поскольку все обновления до этого предполагают, что у вас есть 1000 образцов, когда вы можете только есть 20.
rharter

Было бы неплохо перестать считать после достижения множителя, возможно, так будет быстрее.
inf3rno

9

Ответ Flip в вычислительном отношении более согласован, чем ответ Muis.

Используя формат двойного числа, вы могли увидеть проблему округления в подходе Muis:

Подход Муиса

Когда вы делите и вычитаете, округление появляется в предыдущем сохраненном значении, изменяя его.

Однако подход Flip сохраняет сохраненное значение и уменьшает количество делений, следовательно, уменьшает округление и минимизирует ошибку, распространяемую на сохраненное значение. Добавление только приведет к округлению, если есть что добавить (когда N большое, добавлять нечего)

Подход Flip

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

Я показываю вам результаты с помощью программы для работы с электронными таблицами:

Во-первых, полученные результаты: Полученные результаты

Столбцы A и B представляют собой значения n и X_n соответственно.

Столбец C - это подход Flip, а столбец D - подход Muis, результат сохраняется в виде среднего. Столбец E соответствует среднему значению, используемому в вычислениях.

График, показывающий среднее четных значений, является следующим:

график

Как видите, между обоими подходами есть большие различия.


2
Не совсем ответ, но полезная информация. Было бы даже лучше, если бы вы добавили 3-ю строку к своему графику для истинного среднего по n прошлым значениям, чтобы мы могли видеть, какой из двух подходов подходит ближе всего.
jpaugh

2
@jpaugh: Столбец B чередуется между -1.00E + 15 и 1.00E + 15, поэтому, когда N четное, фактическое среднее значение должно быть 0. Название графика - «Даже частичные средние». Это означает, что третья строка, о которой вы спрашиваете, просто f (x) = 0. График показывает, что оба подхода приводят к ошибкам, которые продолжают расти и расти.
desowin

Это верно, график точно показывает ошибку, распространяемую с использованием больших чисел, участвующих в расчетах с использованием обоих подходов.
Хосе Мануэль Рамос

Легенда вашего графика имеет неправильные цвета: Мюис оранжевый, Флип синий.
xmedeko

6

Пример использования javascript для сравнения:

https://jsfiddle.net/drzaus/Lxsa4rpz/

function calcNormalAvg(list) {
    // sum(list) / len(list)
    return list.reduce(function(a, b) { return a + b; }) / list.length;
}
function calcRunningAvg(previousAverage, currentNumber, index) {
    // [ avg' * (n-1) + x ] / n
    return ( previousAverage * (index - 1) + currentNumber ) / index;
}


1

В Java8:

LongSummaryStatistics movingAverage = new LongSummaryStatistics();
movingAverage.accept(new data);
...
average = movingAverage.getAverage();

у вас есть также IntSummaryStatistics, DoubleSummaryStatistics...


2
OP запрашивает алгоритм, а не указатель, как вычислить это в Java.
olq_plo

0

Изящное решение Python, основанное на приведенных выше ответах:

class RunningAverage():
    def __init__(self):
        self.average = 0
        self.n = 0
        
    def __call__(self, new_value):
        self.n += 1
        self.average = (self.average * (self.n-1) + new_value) / self.n 
        
    def __float__(self):
        return self.average
    
    def __repr__(self):
        return "average: " + str(self.average)

использование:

x = RunningAverage()
x(0)
x(2)
x(4)
print(x)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.