Basis Lab

Answers · Where the data lives

Why can't I just average the old average with the new number to get a running average?

In shortAveraging the old average with the new number gives that single new value half the weight, when it deserves one share out of however many values came before.

A running average cannot be updated by averaging the old average with the new value, because the old average is already standing in for every observation before it and the new value is one observation. The old average has to be weighted by how many values it covers, and the new value by one.

xˉn+1=nn+1 xˉn+1n+1 x\bar{x}_{n+1} = \frac{n}{n+1}\,\bar{x}_n + \frac{1}{n+1}\,x

Take three values, 10, 20 and 30. Their sum is 60, their count is 3, and their mean is 20. A fourth value, 40, arrives. The true mean is now 100/4=25100/4 = 25. Averaging the old mean with the new value gives (20+40)/2=30(20 + 40)/2 = 30, which is five too high. Written out with the weights, the two calculations are:

34(20)+14(40)=15+10=2512(20)+12(40)=10+20=30\frac{3}{4}(20) + \frac{1}{4}(40) = 15 + 10 = 25 \qquad \frac{1}{2}(20) + \frac{1}{2}(40) = 10 + 20 = 30

The weights are the whole story

The guess fails because it asserts that the new value is half of everything you know. That is only true when the old average covers exactly one value, so n=1n = 1 and the first update happens to come out right. Every update after that is wrong. The size of the error can be written down exactly, with nn the count behind the old mean and xx the new value:

error=(x−xˉn)(n−1)2(n+1)\text{error} = \frac{(x - \bar{x}_n)(n-1)}{2(n+1)}

Checking it against the example, x=40x = 40, xˉn=20\bar{x}_n = 20, n=3n = 3, so the error is 20×2/8=520 \times 2 / 8 = 5, matching the five the long way round. Two things follow from the formula. The error grows as nn grows, so the estimate gets worse the longer the stream runs, which is the opposite of what an average should do. And as nn tends to infinity the error settles at (x−xˉn)/2(x - \bar{x}_n)/2, so the reported figure sits permanently halfway between the old mean and the single most recent value, no matter how many values arrive. It never converges.

The same fault appears from the other direction when two partial answers have to be combined. Suppose one partition has seen 60 events summing to 4200, and another has seen 40 events summing to 3800. Averaging their two means gives 82.5; the answer over all 100 events is 80.

Add the sums and add the counts, and the merge is two additions: 4200+3800=80004200 + 3800 = 8000 over 60+40=10060 + 40 = 100. The two means on their own are lossy, since 70 could have come from 7 values summing to 490 or from 7000 values summing to 490000, and nothing distinguishes those cases. That is why the state to keep is the pair (s,n)(s, n) rather than the single number xˉ\bar{x}, and why the pair is the version that survives being split across partitions, recombined out of order, or extended to a correction arriving late.

Where the shortcut does work

There is a sequential form that is correct, xˉ←xˉ+(x−xˉ)/n\bar{x} \leftarrow \bar{x} + (x - \bar{x})/n with nn incremented first, and it produces the same 25 as the weighted form. It is fine for a single stream where events only ever arrive. It stops being fine the moment anything else is asked of it: it carries no count, so two of them cannot be merged, and a window that moves has events leaving as well as arriving, which needs a retraction that a bare mean cannot supply without the count beside it. Keeping sum and count gives you the mean, the merge and the removal, and costs one more number. Repeatedly applying the shortcut also accumulates rounding error, since each step folds its own error into the state and the state is never checked against a fresh total.

Keep the sum and the count. The mean is then a division at the end, and every update, merge and correction is addition.

Worth keeping

  • An average is two numbers, a sum and a count, and the mean is what you get by dividing one by the other at the end.
  • Combining two averages means adding the sums and adding the counts, not averaging the averages.
  • The shortcut of averaging the old average with the new value is correct only on the very first update and gets steadily worse after that.

Where this goes next

This question is one step of Counting Things as They Arrive, a course that works through its subject from the beginning. The page that teaches this part of it is Totals in Bounded Memory, and it reads the whole way through whether or not you have an account.

What an account changes is the practice: problems standing under every idea, marked with a reason rather than a tick, in a copy of the course that is yours.

Read that pageThe whole course

Other questions people ask about this

  1. Why does my count come out doubled after the job restarts?Your count doubled because the restart replayed events the saved counter already held: the state and input position were not saved as one snapshot.
  2. Should I use a sliding window or a fixed window?Use a fixed window when the question names a period that must add up, and a sliding one when it asks about the recent past as of now. Questions about episodes want neither.
  3. How do you count unique visitors without remembering every visitor?Turn each visitor identifier into a random-looking code, keep only a small summary of the rarest pattern seen, and scale it up; repeats change nothing and memory stays fixed.
  4. How does a streaming system know when a window is finished?It cannot know. It guesses: it takes the latest event time it has seen, subtracts an allowance you choose, and treats every window ending before that point as finished.
  5. Why is the date off by one day after I save it and read it back?You saved a calendar date as if it were a precise moment, so showing it in another time zone can move it to the day before or after.
  6. Is UTF-8 the same thing as Unicode, or what's the difference?No: Unicode assigns a number to each character, while UTF-8 is a way of writing those numbers as bytes, using one to four bytes per character.

Read alongside