Як обчислити ковзну середню без збереження підрахунку та загальної кількості даних?


118

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

Я придумав два алгоритми, але обидва повинні зберігати кількість:

  • новий середній = ((старий рахунок * старі дані) + наступні дані) / наступний
  • новий середній = старий середній + (наступні дані - старе середнє) / наступний підрахунок

Проблема цих методів полягає в тому, що кількість збільшується і збільшується, що призводить до втрати точності в середньому.

Перший метод використовує старий підрахунок та наступний підрахунок, які, очевидно, одна на одну. Це змусило мене подумати, що, можливо, є спосіб зняти лічильник, але, на жаль, я його ще не знайшов. Хоча це мене трохи пішло далі, в результаті чого другий метод, але все ще існує.

Це можливо чи я просто шукаю неможливе?


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

1
Дивіться Wikipedia 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;
Даміан

20
Це не зовсім правильно. Те, що описує @Muis, - це експоненціально зважене середнє рухоме значення, яке іноді доречно, але це не саме те, про що вимагала ОП. Як приклад, розгляньте поведінку, яку ви очікуєте, коли більшість балів знаходяться в діапазоні від 2 до 4, але одне значення перевищує мільйон. EWMA (тут) утримуватиме сліди цього мільйона ще довгий час. Кінцева згортка, як вказує ОП, втратить її відразу після N кроків. Він має перевагу постійного зберігання.
jma

9
Це не ковзна середня. Те, що ви описуєте, - це однополюсний фільтр, який створює експоненціальні реакції на стрибки сигналу. Ковзна середня величина створює лінійну відповідь довжиною N.
ruhig brauner

3
Будьте уважні, що це досить далеко від загального визначення середнього. Якщо встановити N = 5 і ввести 5 5зразків, середнє значення буде 0,67.
Дан Даскалеску

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нових значень, що використовуються для обчислення нового середнього.
Патрік Голі

9
Трохи ефективніше для першого: new_average = (old_average * (n-1) + new_value) / n- Видаляє один з розділів.
Pixelstix

Як щодо середнього бігу 3 елементів з 6,0,0,9?
Рошан Мехта

1
Коли я реалізую це рівняння, значення або середнє значення завжди повільно зростає. Це ніколи не спадає - тільки вгору.
anon58192932

30

З блогу про вибіркові вибіркові дисперсії, де середнє значення також розраховується за методом Велфорда :

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

Шкода, що ми не можемо завантажити SVG-зображення.


3
Це схоже на те, що здійснив Муїс, за винятком того, що в розділі використовується загальний фактор. Таким чином, лише один поділ.
Переверніть

Це насправді ближче до @ Abdullah-Al-Ageel (по суті комутативна математика) в тому, що Муїс не враховує збільшення N; посилання формули копію-вставки: [Сер. у н.] = [Сер. у н.-1] + (х - [Сер. у н. 1]) / п.
drzaus

2
@Flip & drwaus: Чи не рішення Муїса та Абдулла Аль-Агееля точно однакові? Це те саме обчислення, просто написане по-іншому. Для мене ці 3 відповіді тотожні, ця більш візуальна (шкода, що ми не можемо використовувати MathJax для SO).
користувач276648

21

Ось ще одна відповідь, що пропонує коментар до того, як відповіді Муїса , Абдулла Аль-Агееля та Фліпа - це математично одне і те ж, крім написаного по-іншому.

Звичайно, у нас є аналіз Хосе Мануеля Рамоса , який пояснює, як помилки округлення впливають на кожну дещо по-різному, але це залежить від реалізації та зміниться залежно від того, як кожна відповідь була застосована до коду.

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

Це в MUIS 's N, Фліп ' s k, і Абдулла аль-Ageel «s n. Абдулла аль-Ageel не цілком пояснює те , що 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
Добре пояснено. Я просто пропускаю звичайний середній показник у вашому графіку, тому що те, про що попросила ОП.
xmedeko

Можливо, мені чогось не вистачає, але ти це випадково мав на увазі max(counter, FACTOR). min(counter, FACTOR)завжди поверне ФАКТОР, правда?
WebWanderer

1
Я вважаю, що справа min(counter, FACTOR)полягає в тому, щоб враховувати період прогріву. Без цього, якщо ваш ФАКТОР (або N, або кількість бажаних зразків) становить 1000, вам знадобиться щонайменше 1000 проб, перш ніж отримати точний результат, оскільки всі оновлення до цього передбачають, що у вас є 1000 зразків, коли ви можете тільки мати 20.
репер

Було б непогано перестати рахувати після досягнення фактора, напевно, так би швидше.
inf3rno

8

Відповідь Фліпа обчислювально більш послідовна, ніж муїська.

Використовуючи формат подвійних чисел, ви можете побачити проблему заокруглення в підході Muis:

Муїський підхід

Під час ділення та віднімання у попередньому збереженому значенні з'являється округлення, змінюючи його.

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

Підхід Фліп

Ці зміни чудові, коли середні великі величини тягнуться до нуля.

Я показую вам результати за допомогою програми електронних таблиць:

По-перше, отримані результати: Результати

Стовпці A і B - це n та X_n значення відповідно.

Стовпець С - це підхід Flip, а D - підхід Муїса, результат зберігається в середньому. Стовпець E відповідає середньому значенню, яке використовується в обчисленні.

Графік, що показує середнє значення парних значень, є наступним:

Графік

Як бачите, великі відмінності між обома підходами.


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

2
@jpaugh: Стовпець B чергується від -1,00E + 15 до 1,00E + 15, тож коли N парне, фактичне середнє значення повинно бути 0. Назва графіка - "Навіть часткові засоби". Це означає, що 3-й рядок, про який ви запитуєте, просто 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.