Monocurl Essays
Series Download

Mean, median and standard deviation: what each one says

The mean is a balance point, the median is a middle, and the standard deviation is a typical distance. Each is the best answer to a precise question.

23 min·High school·

Five weights sit on a beam at 2, 4, 4, 5 and 10. The pivot slides underneath. With the pivot at 2 the beam tips right, at 8 it tips left, and it is level only at 5, which is the mean of the five numbers.
Monocurl source
let X0 = -3.36
let U = 0.56
let PY = -0.4
let R = 0.27
let Seesaw = |c| {
    let raw = sum(map(DATA, |x| x - c))
    var pull = raw
    if (abs(raw) < 0.05) {
        pull = 0
    }
    let th = -0.005 * raw
    let piv = [X0 + c * U, PY, 0]
    let on = |v, h| [piv[0] + (v - c) * U * cos(th) - h * sin(th), piv[1] + (v - c) * U * sin(th) + h * cos(th), 0]
    var parts = [
        Region([piv, [piv[0] - 0.3, PY - 0.6, 0], [piv[0] + 0.3, PY - 0.6, 0]], SLATE),
        Seg(on(-0.3, 0), on(12.3, 0), INK, 5)
    ]
    var seen = []
    for (x in DATA) {
        let below = count(seen, |v| v == x)
        let p = on(x, 0.06 + R + below * 2 * R)
        parts .= Disc(p, R, AZURE, 0.25, 2.5)
        parts .= center{p} Math(to_string(x), 0.72, AZURE)
        seen .= x
    }
    parts .= center{[-1.75, 1.82, 0]} scale{1.2} color{INK} Number(c, 1)
    parts .= center{[3.2, 1.82, 0]} scale{1.2} color{EMBER} Number(pull, 1)
    return parts
}
mesh ground = Ruler(X0, U, 0, 12, 1, PY - 0.6, "")
mesh labels = [
    center{[-2.75, 1.82, 0]} Cap("pivot at", 0.78, INK),
    center{[1.3, 1.82, 0]} Cap("net pull to the right", 0.78, EMBER)
]
mesh beam = Seesaw(c: 2)
mesh verdict = []

slide "balance"
    play Wait(0.8)
    beam.c = 8
    play Lerp(2.6, [&beam])
    play Wait(0.4)
    beam.c = 5
    play Lerp(2.2, [&beam])
    verdict = [center{[0.6, 1.25, 0]} Cap("level at 5, the mean", 0.85, LEAF)]
    play Fade(0.5, [&verdict])
    play Wait(1.6)

Uses the shared definitions for this lesson's figures.

A small company has ten employees. Nine of them earn $40,000 a year and the founder pays herself $4,000,000. A recruiter tells you, truthfully, that the average salary there is $436,000.

The arithmetic is fine: nine times 40 plus 4000 is 4360 thousand, and dividing by ten gives 436 thousand. Yet nobody at the company earns anything close to that. Nine people earn less than a tenth of it and one earns nine times as much.

So what should a summary of a list of numbers be? There are several standard answers, and they disagree. This lesson proves that each is the best possible answer to a specific question, so that choosing between them becomes a matter of choosing the question.

The mean is a balance point

Ten salaries are awkward to compute with, so I will build the ideas on a smaller data set and come back to the company. On five mornings I waited for a bus, and the waits in minutes were

$$ 2,\quad 4,\quad 4,\quad 5,\quad 10. $$

The mean is the total divided by the count: $\frac{2 + 4 + 4 + 5 + 10}{5} = \frac{25}{5} = 5$ minutes. One way to read it: if all five waits had been equal and had added up to the same 25 minutes, each would have been 5.

The figure at the top gives a second reading. Put a weight on a beam at each data value and look for the place where the beam balances. A weight pulls its side down in proportion to its distance from the pivot, so the beam is level when the distances on the left add up to the same as the distances on the right. With the pivot at 5, the left side has distances $3, 1, 1$, which total 5, and the right side has the single distance 5. The value sitting on the pivot contributes nothing.

In symbols, write $x_1, \dots, x_n$ for the data and call $x_i - c$ the deviation of $x_i$ from a number $c$. Deviations are signed: negative to the left of $c$ and positive to the right. Balance at $c$ means that the deviations add up to zero.

The theorem also explains the recruiter’s number. A weight far out on a beam has a long lever arm, so one salary of 4000 on the right needs a great deal on the left to hold it level. Nine salaries sitting close together cannot provide that unless the pivot moves a long way toward the heavy end.

The ten salaries, in thousands of dollars. Nine dots are stacked at 40 and one sits at 4000. The mean, 436, lies in the empty stretch between them, where nobody is. The median is 40.
The ten salaries, in thousands of dollars. Nine dots are stacked at 40 and one sits at 4000. The mean, 436, lies in the empty stretch between them, where nobody is. The median is 40.
Monocurl source
let INC = [40, 40, 40, 40, 40, 40, 40, 40, 40, 4000]
let X0 = -3.2
let U = 6.5 / 4000
let LY = -1.1
let M = mean(INC)
let SORTED = sort(INC)
let MED = (SORTED[4] + SORTED[5]) / 2
mesh line = Ruler(X0, U, 0, 4000, 1000, LY, "salary (thousands of dollars)")
mesh dots = Stack(INC, X0, U, LY, INK, 0.11)
mesh marks = [
    Seg([X0 + M * U, LY, 0], [X0 + M * U, 0.55, 0], EMBER, 4),
    center{[X0 + M * U + 0.95, 0.55, 0]} Cap(str_join(["mean ", to_string(M, 0)]), 0.85, EMBER),
    center{[X0 + 0.75, 1.75, 0]} Cap(str_join(["median ", to_string(MED, 0)]), 0.85, AZURE),
    center{[X0 + 0.95, 1.15, 0]} Cap("nine people", 0.78, INK),
    center{[X0 + 4000 * U - 0.3, LY + 0.5, 0]} Cap("one person", 0.78, INK),
    Tip([X0 + M * U + 0.3, -0.1, 0], [X0 + 4000 * U, -0.1, 0], SLATE, 2.5, 0.16),
    Tip([X0 + M * U + 0.3, -0.1, 0], [X0 + M * U + 0.08, -0.1, 0], SLATE, 2.5, 0.16),
    center{[1.0, 0.22, 0]} Note("3564 from the mean to the founder", 0.76)
]

Uses the shared definitions for this lesson's figures.

The median is the middle

A different summary ignores how far away the values are and only asks about their order. Sort the data and take the value in the middle. For the bus waits, already sorted, the third of the five is 4.

Either way, at least half of the values are less than or equal to the median and at least half are greater than or equal to it.

Pause and try

Find the median salary at the company. Then say what happens to the mean and to the median if the founder raises her own pay from 4 million to 40 million.

Show answerHide answer

Sorted, the ten salaries are nine 40s followed by 4000. The two middle values, the fifth and sixth, are both 40, so the median is 40 thousand. After the raise the total is $360 + 40{,}000 = 40{,}360$ thousand and the mean is 4036 thousand, almost ten times what it was. The fifth and sixth values are still 40, so the median does not move at all.

The same experiment on the bus data shows the difference in behavior. Suppose the worst morning had been worse.

The longest of the five waits grows from 10 minutes to 24 while the other four stay put. The mean is pulled along behind it, from 5.0 to 7.8. The median stays at 4, because the middle value in the sorted list has not changed.
Monocurl source
let X0 = -3.3
let U = 0.254
let LY = -1.15
let Drag = |top| {
    let xs = [2, 4, 4, 5, top]
    let m = sum(xs) / 5
    let med = sort(xs)[2]
    return [
        Seg([X0 + med * U, LY, 0], [X0 + med * U, 1.75, 0], AZURE, 3.5),
        center{[X0 + med * U - 0.85, 1.6, 0]} Cap("median", 0.9, AZURE),
        center{[X0 + med * U - 0.85, 1.05, 0]} scale{1.5} color{AZURE} Number(med, 1),
        Seg([X0 + m * U, LY, 0], [X0 + m * U, 0.75, 0], EMBER, 3.5),
        center{[X0 + m * U + 0.75, 0.6, 0]} Cap("mean", 0.9, EMBER),
        center{[X0 + m * U + 0.75, 0.05, 0]} scale{1.5} color{EMBER} Number(m, 1),
        Stack([2, 4, 4, 5], X0, U, LY, INK, 0.125),
        Seg([X0 + top * U, LY, 0], [X0 + top * U, -0.35, 0], ROSE, 3.5),
        Pt([X0 + top * U, LY + 0.175, 0], ROSE, 0.15),
        center{[2.45, 1.05, 0]} scale{1.5} color{ROSE} Number(top, 0)
    ]
}
mesh line = Ruler(X0, U, 0, 26, 2, LY, "wait (minutes)")
mesh fixed = center{[2.45, 1.6, 0]} Cap("longest wait", 0.9, ROSE)
mesh drag = Drag(top: 10)

slide "drag"
    play Wait(1)
    drag.top = 24
    play Lerp(4.5, [&drag])
    play Wait(1.6)

Uses the shared definitions for this lesson's figures.

A value far from the rest is called an outlier. The mean feels every outlier in proportion to its distance. The median only registers which side of the middle a value is on, so a wait of 10, 24 or 2400 minutes counts the same. Statisticians say the median is robust.

It is tempting to conclude that the median is the honest one. That is too quick, because the insensitivity that protects the median also makes it blind. If you run the bus company and need the total delay across all passengers, the total is $n$ times the mean, and the median cannot give it to you. A lottery ticket has a median prize of zero, which tells you nothing about what the lottery pays out. Each summary answers a question, and the next section says which.

What each one is best at

Suppose you must replace the whole data set by a single number $c$, and you will be charged for every value you misrepresent. How you are charged decides which $c$ is best. Two natural charges are the distance $|x_i - c|$ and the squared distance $(x_i - c)^2$.

Pause and try

For the bus data $2, 4, 4, 5, 10$, compute the total distance $\sum |x_i - c|$ for $c = 4$ and for $c = 5$. Then compute the total squared distance $\sum (x_i - c)^2$ for the same two choices. Which $c$ wins under each charge?

Show answerHide answer

Total distance: at $c = 4$ it is $2 + 0 + 0 + 1 + 6 = 9$, and at $c = 5$ it is $3 + 1 + 1 + 0 + 5 = 10$, so the median wins. Total squared distance: at $c = 4$ it is $4 + 0 + 0 + 1 + 36 = 41$, and at $c = 5$ it is $9 + 1 + 1 + 0 + 25 = 36$, so the mean wins.

Two values of $c$ prove nothing about all the others, so here are both charges for every $c$ from 0 to 12.

Both charges for the bus data, as the single summary number $c$ sweeps from 0 to 12. The total distance (left) is made of straight pieces and is lowest at $c = 4$, the median. The total squared distance (right) is a parabola and is lowest at $c = 5$, the mean.
Monocurl source
let fa = Frame(0, 12, 0, 40, [-1.75, 0, 0], 2.9, 2.6)
let fs = Frame(0, 12, 0, 300, [2.1, 0, 0], 2.9, 2.6)
let Panel = |f, ys, name, col| [
    GridLines(f, [2, 4, 6, 8, 10, 12], ys),
    Axes(f),
    XTicks(f, [4, 8, 12]),
    YTicks(f, ys),
    map(DATA, |x| Seg([fx(f, x), fy(f, 0), 0], [fx(f, x), fy(f, 0) + 0.16, 0], INK, 4)),
    center{[fx(f, 6), fy(f, 0) - 0.68, 0]} Note("summary number c", 0.72),
    center{[fx(f, 6), fy(f, ys[len(ys) - 1]) + 0.42, 0]} Cap(name, 0.76, col)
]
let Sweep = |c| {
    let n = to_int(max(2, ceil(c * 12)))
    let cs = sample(0, max(c, 0.05), n)
    return [
        stroke{AZURE, 4} Polyline(map(cs, |t| at(fa, t, total_dist(DATA, t)))),
        stroke{EMBER, 4} Polyline(map(cs, |t| at(fs, t, total_sq(DATA, t)))),
        Pt(at(fa, c, total_dist(DATA, c)), AZURE, 0.09),
        Pt(at(fs, c, total_sq(DATA, c)), EMBER, 0.09)
    ]
}
mesh panels = [
    Panel(fa, [10, 20, 30, 40], "total distance", AZURE),
    Panel(fs, [100, 200, 300], "total squared distance", EMBER)
]
mesh sweep = Sweep(c: 0.05)
mesh lows = []

slide "sweep"
    play Wait(0.5)
    sweep.c = 12
    play Lerp(5, [&sweep], linear)
    lows = [
        Ring(at(fa, 4, 9), LEAF, 0.12, 4),
        center{at(fa, 5.6, 36.5)} Cap("lowest: 9, at c = 4", 0.72, LEAF),
        Ring(at(fs, 5, 36), LEAF, 0.12, 4),
        center{at(fs, 5.2, 274)} Cap("lowest: 36, at c = 5", 0.72, LEAF)
    ]
    play Fade(0.6, [&lows])
    play Wait(1.8)

Uses the shared definitions for this lesson's figures.

The right-hand curve looks like a parabola with its lowest point at the mean. That can be proved in two lines, and the proof gives the exact height of the curve everywhere.

Check it against the numbers you computed: at $c = 4$ the identity predicts $36 + 5 \cdot (5 - 4)^2 = 41$. The balance property did all the work. It is what made the cross terms cancel.

The left-hand curve is made of straight pieces with corners at the data values, and its lowest point is at the median. Algebra with absolute values is clumsy, so we need a different idea, and the figure below shows it. Look at the smallest and largest values together.

  1. Put c at 7. Its distances to the outermost values, 2 and 10, are 5 and 3. Together they make 8.
  2. Slide c to 3. The distances are now 1 and 7, still 8 together: the two pieces tile the gap from 2 to 10.
  3. Push c outside the pair, to 11.5. Now the pieces overlap, and together they make 11.
  4. The next pair in, 4 and 5, behaves the same way. With c at 4.5 it contributes 1, the gap between them.
  5. The middle value, 4, is left over. It contributes nothing only if c equals it. At c = 4 every row is as small as it can be.
Why the median gives the smallest total distance. The data are paired from the outside in. A pair contributes exactly the gap between its two values whenever $c$ lies between them, and more when $c$ is outside.
Monocurl source
let X0 = -3.4
let U = 0.47
let LY = -1.1
let Row = |c, lo, hi, y, col| [
    Seg([X0 + lo * U, y - 0.16, 0], [X0 + lo * U, y + 0.16, 0], col, 3),
    Seg([X0 + hi * U, y - 0.16, 0], [X0 + hi * U, y + 0.16, 0], col, 3),
    Span([X0 + lo * U, y + 0.07, 0], [X0 + c * U, y + 0.07, 0], col, 8),
    Span([X0 + c * U, y - 0.07, 0], [X0 + hi * U, y - 0.07, 0], tint(col, 0.55), 8),
    center{[3.2, y, 0]} scale{1.25} color{col} Number(abs(lo - c) + abs(hi - c), 1)
]
let Marker = |c| [
    Seg([X0 + c * U, LY, 0], [X0 + c * U, 1.75, 0], INK, 2.5),
    center{[X0 + c * U, 1.98, 0]} Math("c", 0.9, INK)
]
mesh line = Ruler(X0, U, 0, 12, 1, LY, "wait (minutes)")
mesh dots = Stack(DATA, X0, U, LY, INK, 0.11)
mesh head = center{[3.2, 1.95, 0]} Cap("distance", 0.76, SLATE)
mesh outer = []
mesh inner = []
mesh mid = []
mesh marker = Marker(c: 7)
mesh result = []

slide "Put c at 7. Its distances to the outermost values, 2 and 10, are 5 and 3. Together they make 8."
    outer = Row(c: 7, 2, 10, 1.25, AZURE)
    play Fade(0.7)

slide "Slide c to 3. The distances are now 1 and 7, still 8 together: the two pieces tile the gap from 2 to 10."
    outer.c = 3
    marker.c = 3
    play Lerp(1.6, [&outer, &marker])

slide "Push c outside the pair, to 11.5. Now the pieces overlap, and together they make 11."
    outer.c = 11.5
    marker.c = 11.5
    play Lerp(1.8, [&outer, &marker])

slide "The next pair in, 4 and 5, behaves the same way. With c at 4.5 it contributes 1, the gap between them."
    outer.c = 4.5
    marker.c = 4.5
    play Lerp(1.6, [&outer, &marker])
    inner = Row(c: 4.5, 4, 5, 0.6, EMBER)
    play Fade(0.6, [&inner])

slide "The middle value, 4, is left over. It contributes nothing only if c equals it. At c = 4 every row is as small as it can be."
    outer.c = 4
    inner.c = 4
    marker.c = 4
    play Lerp(1.2, [&outer, &inner, &marker])
    mid = [
        Pt([X0 + 4 * U, -0.05, 0], PLUM, 0.11),
        center{[3.2, -0.05, 0]} scale{1.25} color{PLUM} Number(0, 1)
    ]
    result = [center{[1.6, -0.6, 0]} Math("8 + 1 + 0 = 9", 1.0, LEAF)]
    play Fade(0.6, [&mid, &result])

Uses the shared definitions for this lesson's figures.

Neither is the “true” average. Under the squared charge, missing the founder’s salary by 3564 costs as much as missing each of 81 colleagues by 396, so the mean gives up nine people to get closer to one. Under the plain charge every thousand dollars of error costs the same, and the best policy is to stand with the majority.

Measuring spread

A center is only half a summary. Suppose a second bus route had waits of $4, 5, 5, 5, 6$ minutes. Its mean is also 5, but on that route you can plan your morning, and on mine you cannot. We want a number that says how far the data typically are from their center.

The obvious candidate is the average deviation from the mean. It fails for a reason we have already proved: the deviations always sum to zero, so their average is zero for every data set. The negative deviations cancel the positive ones, and we have to stop them cancelling.

One repair is to take absolute values, and the average of $|x_i - \bar x|$ is a legitimate measure called the mean absolute deviation. The other repair is to square. The last section says which one belongs with the mean: the mean makes the total of squared deviations as small as possible, so that smallest total measures how badly the mean fits. Squares also expand algebraically, as in the proof above, and that will later give a rule that absolute deviations lack: the variances of independent quantities add.

  1. Each wait has a deviation from the mean: -3, -1, -1, 0 and +5. They add up to zero, as they must.
  2. Square each deviation, so that none is negative. As areas: 9, 1, 1, 0 and 25 square minutes.
  3. The areas total 36. Shared equally among the five waits, that is 7.2 square minutes each: the variance.
  4. The side of that average square is 2.68 minutes, the standard deviation: a length, which can be laid on the number line.
The squared deviations of the bus data, drawn as actual squares at the same scale as the number line. Their average area is the variance, 7.2 square minutes, and the side of a square with that area is the standard deviation, about 2.68 minutes.
Monocurl source
let X0 = -3.6
let U = 0.34
let LY = -0.5
let MU = mean(DATA)
let SS = total_sq(DATA, MU)
let VAR = SS / len(DATA)
let SD = sqrt(VAR)
let ROWS = [1.6, 1.2, 0.8, 0.4, 0.0]
let dev_text = |d| {
    if (d > 0) {
        return str_join(["+", to_string(d, 0)])
    }
    return to_string(d, 0)
}
let Sq = |corner, side, col| Shape([corner, [corner[0] + side, corner[1], 0], [corner[0] + side, corner[1] + side, 0], [corner[0], corner[1] + side, 0]], col, 0.3, 3)
mesh line = Ruler(X0, U, 0, 10, 2, LY, "wait (minutes)")
mesh centre = [
    Seg([X0 + MU * U, LY, 0], [X0 + MU * U, 1.85, 0], LEAF, 3),
    center{[X0 + MU * U, 2.05, 0]} Cap("mean", 0.74, LEAF)
]
mesh devs = map(range(0, 5), |i| [
    Span([X0 + MU * U, ROWS[i], 0], [X0 + DATA[i] * U, ROWS[i], 0], [AZURE, EMBER][DATA[i] > MU], 5),
    Pt([X0 + DATA[i] * U, ROWS[i], 0], INK, 0.09),
    center{[X0 + DATA[i] * U + [-0.42, 0.42][DATA[i] >= MU], ROWS[i], 0]} Math(dev_text(DATA[i] - MU), 0.78, [AZURE, EMBER][DATA[i] > MU])
])
mesh squares = []
mesh zero = [center{[2.1, 0.9, 0]} Math("-3 - 1 - 1 + 0 + 5 = 0", 0.9, INK)]
mesh calc = []
mesh band = []

slide "Each wait has a deviation from the mean: -3, -1, -1, 0 and +5. They add up to zero, as they must."
    play Fade(0.6)

slide "Square each deviation, so that none is negative. As areas: 9, 1, 1, 0 and 25 square minutes."
    zero = []
    play Fade(0.3, [&zero])
    squares = [
        Sq([0.6, -1.95, 0], 3 * U, AZURE),
        center{[0.6 + 1.5 * U, -1.95 + 1.5 * U, 0]} Math("9", 0.9, AZURE),
        Sq([0.6, -0.62, 0], U, AZURE),
        Sq([1.2, -0.62, 0], U, AZURE),
        center{[0.6 + U / 2, -0.05, 0]} Math("1", 0.8, AZURE),
        center{[1.2 + U / 2, -0.05, 0]} Math("1", 0.8, AZURE),
        Sq([2.0, -1.95, 0], 5 * U, EMBER),
        center{[2.0 + 2.5 * U, -1.95 + 2.5 * U, 0]} Math("25", 0.95, EMBER)
    ]
    play Fade(0.7, [&squares])

slide "The areas total 36. Shared equally among the five waits, that is 7.2 square minutes each: the variance."
    calc = [
        center{[2.15, 1.9, 0]} Math(str_join(["9 + 1 + 1 + 0 + 25 = ", to_string(SS, 0)]), 0.8, INK),
        Sq([0.75, 0.42, 0], SD * U, LEAF),
        center{[0.75 + SD * U / 2, 0.42 + SD * U / 2, 0]} Math(to_string(VAR, 1), 0.82, LEAF),
        center{[2.7, 0.88, 0]} Math(str_join(["\frac{", to_string(SS, 0), "}{5} = ", to_string(VAR, 1)]), 0.9, LEAF)
    ]
    play Fade(0.7, [&calc])

slide "The side of that average square is 2.68 minutes, the standard deviation: a length, which can be laid on the number line."
    band = [
        Seg([X0 + (MU - SD) * U, -1.5, 0], [X0 + (MU + SD) * U, -1.5, 0], LEAF, 7),
        Seg([X0 + MU * U, -1.62, 0], [X0 + MU * U, -1.38, 0], LEAF, 3),
        center{[X0 + MU * U, -1.88, 0]} Math(str_join(["5 \pm ", to_string(SD, 2)]), 0.82, LEAF),
        center{[2.7, 0.25, 0]} Math(str_join(["\sqrt{", to_string(VAR, 1), "} \approx ", to_string(SD, 2)]), 0.9, LEAF)
    ]
    play Fade(0.7, [&band])

Uses the shared definitions for this lesson's figures.

The square root is there for the units. Squared deviations of waits are in square minutes, so the variance of my bus data is 7.2 square minutes, a quantity that cannot be compared with a wait. Its square root, $\sqrt{7.2} \approx 2.68$ minutes, can. Read it as a typical distance from the mean, with the understanding that “typical” here is a root of a mean of squares, which weights large deviations more heavily than a plain average would. The plain average of the distances $3, 1, 1, 0, 5$ is 2, and the standard deviation is 2.68.

Pause and try

Compute the variance and standard deviation of the second route, $4, 5, 5, 5, 6$.

Show answerHide answer

The mean is 5 and the deviations are $-1, 0, 0, 0, 1$. Their squares total 2, so the variance is $\frac25 = 0.4$ square minutes and the standard deviation is $\sqrt{0.4} \approx 0.63$ minutes.

The two routes on the same scale. Both have mean 5. The shaded band reaches one standard deviation either side of the mean: 2.68 minutes on route A and 0.63 minutes on route B.
The two routes on the same scale. Both have mean 5. The shaded band reaches one standard deviation either side of the mean: 2.68 minutes on route A and 0.63 minutes on route B.
Monocurl source
let X0 = -3.6
let U = 0.47
let ROUTE_B = [4, 5, 5, 5, 6]
let Route = |xs, y, name| {
    let m = mean(xs)
    let sd = sqrt(total_sq(xs, m) / len(xs))
    return [
        Region([[X0 + (m - sd) * U, y, 0], [X0 + (m + sd) * U, y, 0], [X0 + (m + sd) * U, y + 1.0, 0], [X0 + (m - sd) * U, y + 1.0, 0]], tint(LEAF, 0.25)),
        Seg([X0 + m * U, y, 0], [X0 + m * U, y + 1.0, 0], LEAF, 3),
        Ruler(X0, U, 0, 12, 1, y, ""),
        Stack(xs, X0, U, y, INK, 0.12),
        center{[X0 + 0.6, y + 0.85, 0]} Cap(name, 0.8, INK),
        center{[3.05, y + 0.72, 0]} Cap("standard", 0.74, LEAF),
        center{[3.05, y + 0.42, 0]} Cap("deviation", 0.74, LEAF),
        center{[3.05, y + 0.02, 0]} Math(to_string(sd, 2), 0.95, LEAF)
    ]
}
mesh a = Route(DATA, 0.75, "route A")
mesh b = Route(ROUTE_B, -1.35, "route B")
mesh name = center{[X0 + 6 * U, -2.05, 0]} Note("wait (minutes)", 0.72)

Uses the shared definitions for this lesson's figures.

Dividing by $n$ or by $n - 1$

Most calculators have two standard deviation buttons. One divides the sum of squared deviations by $n$, as in the definition above. The other divides by $n - 1$. For the bus data they give $\sqrt{36/5} \approx 2.68$ and $\sqrt{36/4} = 3$. Which is right depends on what the data are.

If the five waits are all the data there is, and you only want to describe them, divide by $n$. The definition above is the right one and there is nothing more to say.

Usually, though, the data are a sample: a few values drawn from a much larger population, such as all the mornings I could have waited. That population has its own mean $\mu$ and its own variance $\sigma^2$, both unknown, and the sample is being used to estimate them. Here a problem appears. The population variance is the average of $(x - \mu)^2$, with deviations measured from the true mean $\mu$. We do not know $\mu$, so we measure from the sample mean $\bar x$ instead. But $\bar x$ is the number that makes the sum of squared deviations of this sample as small as possible. The identity we proved, with $c = \mu$, says exactly how much smaller:

$$ \sum_{i=1}^n (x_i - \bar x)^2 = \sum_{i=1}^n (x_i - \mu)^2 - n(\bar x - \mu)^2. $$

Measuring from $\bar x$ therefore always gives a sum that is too small, unless the sample mean happens to equal $\mu$. Dividing that sum by $n$ underestimates the population variance on average.

The size of the shortfall can be computed. Averaged over many samples, each term $(x_i - \mu)^2$ is $\sigma^2$, by the definition of the population variance, so the first sum on the right averages $n\sigma^2$. For independent draws the sample mean has variance $\sigma^2/n$; I am borrowing this fact, which is proved in the lesson on sampling. It says that $(\bar x - \mu)^2$ averages $\sigma^2/n$, so the subtracted term averages $\sigma^2$. Hence the sum of squared deviations from $\bar x$ averages $n\sigma^2 - \sigma^2 = (n - 1)\sigma^2$, and dividing it by $n - 1$, not $n$, gives a quantity that averages exactly $\sigma^2$.

A claim about averages over many samples can be tested by taking many samples. A fair die is a population we know completely: its mean is 3.5 and its variance is $\frac{35}{12} \approx 2.92$.

A simulation. For each sample size from 2 to 10, a fair die was rolled to make 4000 samples, and both formulas were applied to each sample. Dividing by n gives estimates whose average falls short of the true variance, by the factor (n-1)/n drawn as the red curve. Dividing by n - 1 gives estimates whose average sits on the true value.
A simulation. For each sample size from 2 to 10, a fair die was rolled to make 4000 samples, and both formulas were applied to each sample. Dividing by $n$ gives estimates whose average falls short of the true variance, by the factor $(n-1)/n$ drawn as the red curve. Dividing by $n - 1$ gives estimates whose average sits on the true value.
Monocurl source
let S = 4000
let TRUE_VAR = 35 / 12
var by_n = []
var by_n1 = []
for (n in range(2, 11)) {
    var a = 0
    var b = 0
    for (s in range(0, S)) {
        var t = 0
        var q = 0
        for (j in range(0, n)) {
            let x = roll(s * 10 + j + 1, n)
            t = t + x
            q = q + x * x
        }
        let ss = q - t * t / n
        a = a + ss / n
        b = b + ss / (n - 1)
    }
    by_n .= a / S
    by_n1 .= b / S
}
let BY_N = by_n
let BY_N1 = by_n1
let fv = Frame(1, 10.6, 0, 3.6, [-0.05, 0.05, 0], 6.2, 3.0)
mesh frame = [
    GridLines(fv, [2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3]),
    Axes(fv),
    map(range(2, 11), |n| center{[fx(fv, n), fy(fv, 0) - 0.3, 0]} Math(to_string(n), 0.72, SLATE)),
    YTicks(fv, [1, 2, 3]),
    center{[fx(fv, 6), fy(fv, 0) - 0.68, 0]} Note("sample size n", 0.74),
    center{[fx(fv, 1) + 1.75, fy(fv, 3.6) + 0.42, 0]} Note("average estimate of the variance", 0.74)
]
mesh truth = stroke{SLATE, 3} dashed{[0.14, 0.09]} Line(at(fv, 1, TRUE_VAR), at(fv, 10.6, TRUE_VAR))
mesh theory = Path(map(sample(1.5, 10.5, 80), |n| at(fv, n, TRUE_VAR * (n - 1) / n)), tint(ROSE, 0.6), 3)
mesh dots = [
    map(range(0, 9), |k| Pt(at(fv, k + 2, BY_N[k]), ROSE, 0.1)),
    map(range(0, 9), |k| Pt(at(fv, k + 2, BY_N1[k]), LEAF, 0.1))
]
mesh labels = [
    center{at(fv, 8.2, 3.3)} Cap("divide by n - 1", 0.78, LEAF),
    center{at(fv, 4.6, 1.55)} Cap("divide by n", 0.78, ROSE),
    center{at(fv, 7.6, 0.75)} Note(str_join(["true variance ", to_string(TRUE_VAR, 2), " (dashed)"]), 0.74),
    center{at(fv, 7.6, 0.3)} Note("simulation: 4000 samples per size", 0.72)
]

Uses the shared definitions for this lesson's figures.

With samples of two rolls, dividing by $n$ gave an average estimate of about 1.49, half the true 2.92. With samples of ten it gave about 2.65, nine tenths of it. The $n - 1$ versions averaged between 2.90 and 2.99 at every size.

Three qualifications are needed. First, the correction matters only for small samples: at $n = 100$ the two standard deviations differ by half a percent. Second, $n - 1$ makes $s^2$ right on average for the variance, and it does not do the same for $s$, because the square root of an average is not the average of the square roots. The sample standard deviation still runs slightly low on average, and in everyday practice that small shortfall is left uncorrected. Third, some correction is needed for a reason that takes no algebra. The $n$ deviations from $\bar x$ sum to zero, so once you know $n - 1$ of them you know the last. The sample contains only $n - 1$ independent pieces of information about spread, called degrees of freedom. A sample of one value has a deviation of zero from its own mean and tells you nothing about spread. The formula with $n$ would report a variance of 0, and the formula with $n - 1$ reports $\frac00$, undefined, which is the truthful answer.

Worked examples

Common mistakes

Stepping back

The difficulty at the start was that “the average” sounded like one thing, and the company showed it is at least two. The idea that resolved it was to ask what each summary is best at. The mean is the unique minimizer of total squared distance, the median minimizes total distance, and the standard deviation is the size of the squared misfit that remains after the mean has done its best. The mean and the standard deviation are a matched pair for that reason, and both follow outliers. The identity behind all of this, $\sum (x_i - c)^2 = \sum (x_i - \bar x)^2 + n(\bar x - c)^2$, will return whenever a total spread is split into parts.

The next lesson takes up the most famous shape a data set can have, the bell curve, for which the mean and standard deviation tell you everything.

Check yourself

A data set has mean 50 and median 30. Which statement must be true?

Problems

The first two repeat the lesson’s computations. The next three ask you to reason with the definitions. The last two prove something new.

Problem 1

For the data $3, 7, 8, 12, 20$, find the mean, the median, the variance and standard deviation (dividing by $n$), and the sample standard deviation (dividing by $n - 1$).

Show solutionHide solution

The total is 50, so the mean is 10. The data are sorted and the third value is 8, the median. The deviations from 10 are $-7, -3, -2, 2, 10$, which sum to 0 as they should. Their squares are $49, 9, 4, 4, 100$, totalling 166. The variance is $\frac{166}{5} = 33.2$ and the standard deviation is $\sqrt{33.2} \approx 5.76$. The sample variance is $\frac{166}{4} = 41.5$ and the sample standard deviation is $\sqrt{41.5} \approx 6.44$.

Problem 2

Six students score $70, 75, 80, 80, 85, 90$ on a test. A seventh, who was ill, scores 10. Find the mean and median before and after the seventh score is included, and explain the difference in how they respond.

Show solutionHide solution

Before: the total is 480, so the mean is 80, and the two middle values are both 80, so the median is 80. After: the total is 490 and the mean is $\frac{490}{7} = 70$. Sorted, the scores are $10, 70, 75, 80, 80, 85, 90$, and the fourth of seven is 80, so the median is still 80. The new score has a deviation of $-70$ from the old mean, and the mean must move far enough to rebalance it. For the median, the new score is one more value on the low side of the middle, and it would count the same if it were 69.

Problem 3

Show that the variance equals “the mean of the squares minus the square of the mean”:

$$ \frac1n\sum_{i=1}^n (x_i - \bar x)^2 = \frac1n\sum_{i=1}^n x_i^2 - \bar x^2. $$

Check it on the bus data $2, 4, 4, 5, 10$.

Show solutionHide solution

Use the identity from the lesson with $c = 0$: $\sum x_i^2 = \sum (x_i - \bar x)^2 + n\bar x^2$. Rearranging and dividing by $n$ gives the formula. (Directly: expand $(x_i - \bar x)^2 = x_i^2 - 2\bar x x_i + \bar x^2$ and sum, using $\sum x_i = n\bar x$, to get $\sum x_i^2 - 2n\bar x^2 + n\bar x^2$.) For the bus data the squares are $4, 16, 16, 25, 100$, totalling 161, so the mean of the squares is 32.2. Subtracting $5^2 = 25$ leaves 7.2, the variance found in the lesson.

Problem 4

For the data $1, 2, 3, 100$: (a) find every number $c$ that minimizes the total distance $\sum |x_i - c|$, and the minimum value; (b) find the $c$ that minimizes the total squared distance.

Show solutionHide solution

(a) Here $n = 4$ is even. Pair 1 with 100 and 2 with 3. The total distance is at least $(100 - 1) + (3 - 2) = 100$, with equality exactly when $c$ lies in both intervals, that is, $2 \le c \le 3$. Every such $c$ is a minimizer, including the median 2.5. Check at $c = 2$: $1 + 0 + 1 + 98 = 100$. (b) The mean, $\frac{106}{4} = 26.5$, a number larger than three of the four data values.

Problem 5

A club has two sections. Section 1 has 10 members with mean age 50; section 2 has 30 members with mean age 70. (a) Find the mean age of the whole club. (b) Show by example that the median age of the whole club cannot be worked out from the two sections’ medians.

Show solutionHide solution

(a) The total of the ages is $10 \times 50 + 30 \times 70 = 2600$, over 40 members: the mean is 65. It is closer to 70 because section 2 has more members. (b) Take small sets to make the point. The sets $\{1, 2, 3\}$ and $\{4, 5, 6\}$ have medians 2 and 5, and combined, $1, 2, 3, 4, 5, 6$, the median is 3.5. The sets $\{1, 2, 9\}$ and $\{4, 5, 6\}$ also have medians 2 and 5, but combined, $1, 2, 4, 5, 6, 9$, the median is 4.5. The same pair of medians leads to different combined medians, so no formula can exist. The mean combines because it is built from totals, and totals add.

Problem 6

Prove that in any data set with standard deviation $\sigma > 0$, and for any $k > 0$, the fraction of values that lie $k\sigma$ or more from the mean is at most $\frac{1}{k^2}$. Then test the bound on the founder’s salary.

Show solutionHide solution

Suppose $m$ of the $n$ values satisfy $|x_i - \bar x| \ge k\sigma$. Each of those contributes at least $k^2\sigma^2$ to the sum of squared deviations, and the others contribute at least 0. So

$$ n\sigma^2 = \sum_{i=1}^n (x_i - \bar x)^2 \ge m k^2 \sigma^2, $$

and dividing by $n k^2 \sigma^2$ gives $\frac mn \le \frac{1}{k^2}$. For instance, at most a quarter of any data set lies two or more standard deviations from its mean. At the company, the founder’s deviation is $3564 = 3 \times 1188$, exactly three standard deviations. The bound with $k = 3$ allows at most $\frac19$ of the values to be that far out, and the actual fraction is $\frac{1}{10}$. This is Chebyshev’s inequality from the law of large numbers, stated for data.

Problem 7

A population consists of the results of a fair coin, scored 0 for tails and 1 for heads, so its mean is $\mu = \frac12$ and its variance is $\sigma^2 = \frac14$. List the four equally likely samples of size 2. For each, compute the variance estimate that divides by $n$ and the one that divides by $n - 1$. Average each estimate over the four samples and compare with $\sigma^2$.

Show solutionHide solution

The samples are $(0,0)$, $(0,1)$, $(1,0)$, $(1,1)$. For $(0,0)$ and $(1,1)$ the sample mean equals both values and the sum of squared deviations is 0. For $(0,1)$ and $(1,0)$ the sample mean is $\frac12$ and the sum of squared deviations is $\frac14 + \frac14 = \frac12$. Dividing by $n = 2$ gives estimates $0, \frac14, \frac14, 0$, which average $\frac18$: half the true variance, as the factor $\frac{n-1}{n} = \frac12$ predicts. Dividing by $n - 1 = 1$ gives $0, \frac12, \frac12, 0$, which average $\frac14 = \sigma^2$ exactly. No single sample gives the right answer, and the $n - 1$ estimates are right on average.

Shared definitions used by this lesson's figures
# house kit: prepended to every essay figure after the std imports

# palette. AZURE and EMBER are the two actors, LEAF marks an answer, ROSE a problem
let PAPER = [1, 1, 1, 1]
let INK = [0.08, 0.09, 0.11, 1]
let SLATE = [0.40, 0.44, 0.50, 1]
let MIST = [0.85, 0.86, 0.88, 1]
let AZURE = [0.13, 0.42, 0.68, 1]
let EMBER = [0.89, 0.42, 0.12, 1]
let LEAF = [0.13, 0.55, 0.36, 1]
let ROSE = [0.78, 0.22, 0.24, 1]
let PLUM = [0.47, 0.30, 0.66, 1]
let GOLD = [0.85, 0.62, 0.08, 1]

background = PAPER

# an opaque pastel of col; use for fills so overlaps do not darken
let tint = |col, amount = 0.18| [
    1 - (1 - col[0]) * amount,
    1 - (1 - col[1]) * amount,
    1 - (1 - col[2]) * amount,
    1
]

# text: Cap for labels, Note for quiet labels, Math for typeset maths
let Cap = |txt, size = 0.75, col = INK| color{col} Text(txt, size, "Charter")
let Note = |txt, size = 0.65| color{SLATE} Text(txt, size, "Charter")
let Math = |tex, size = 0.9, col = INK| color{col} Tex(tex, size)

# a paper rectangle to sit behind text that crosses lines
let Plate = |pos, size| center{pos} fill{PAPER} stroke{alpha{0} PAPER, 0} Rect(size)

# shapes with fill and stroke both set, so no stray black appears
let Pt = |pos, col = INK, r = 0.075| center{pos} fill{col} stroke{col, 1} Circle(r, 48)
let Ring = |pos, col = INK, r = 0.1, w = 3| center{pos} fill{PAPER} stroke{col, w} Circle(r, 48)
let Seg = |a, b, col = INK, w = 3| stroke{col, w} Line(a, b)
let Path = |pts, col = INK, w = 3| stroke{col, w} Polyline(pts)
let Region = |pts, col| fill{col} stroke{alpha{0} col, 0} Polygon(pts)
let Outline = |pts, col = INK, w = 3| fill{alpha{0} PAPER} stroke{col, w} Polygon(pts)
let Shape = |pts, col, amount = 0.18, w = 3| fill{tint(col, amount)} stroke{col, w} Polygon(pts)
let Disc = |pos, r, col, amount = 0.18, w = 3| center{pos} fill{tint(col, amount)} stroke{col, w} Circle(r, 96)
let Box = |pos, size, col, amount = 0.18, w = 3| center{pos} fill{tint(col, amount)} stroke{col, w} Rect(size)
# a line from a to b ending in a small solid arrowhead
let Tip = |a, b, col = INK, w = 3, head = 0.17| {
    let d = normalize([b[0] - a[0], b[1] - a[1], 0])
    let n = [-d[1], d[0], 0]
    let base = [b[0] - d[0] * head, b[1] - d[1] * head, 0]
    return [
        stroke{col, w} Line(a, base),
        fill{col} stroke{col, 1} Polygon([
            b,
            [base[0] + n[0] * head * 0.42, base[1] + n[1] * head * 0.42, 0],
            [base[0] - n[0] * head * 0.42, base[1] - n[1] * head * 0.42, 0]
        ])
    ]
}

# plots. a frame is [x0, x1, y0, y1, centre, width, height]: a data window and the scene box it fills
let Frame = |x0, x1, y0, y1, c = [0, 0, 0], w = 6.4, h = 3.6| [x0, x1, y0, y1, c, w, h]
let fx = |fr, x| fr[4][0] + (x - (fr[0] + fr[1]) / 2) * fr[5] / (fr[1] - fr[0])
let fy = |fr, y| fr[4][1] + (y - (fr[2] + fr[3]) / 2) * fr[6] / (fr[3] - fr[2])
let at = |fr, x, y| [fx(fr, x), fy(fr, y), 0]

# axes through the data origin (or the nearest edge of the window), with optional names
let Axes = |fr, x_name = "", y_name = "", col = SLATE| {
    let ox = clamp(fr[0], 0, fr[1])
    let oy = clamp(fr[2], 0, fr[3])
    var parts = [
        Tip(at(fr, fr[0], oy), [fx(fr, fr[1]) + 0.25, fy(fr, oy), 0], col, 2.5, 0.15),
        Tip(at(fr, ox, fr[2]), [fx(fr, ox), fy(fr, fr[3]) + 0.25, 0], col, 2.5, 0.15)
    ]
    if (x_name != "") {
        parts .= center{[fx(fr, fr[1]) + 0.5, fy(fr, oy), 0]} Math(x_name, 0.8, col)
    }
    if (y_name != "") {
        parts .= center{[fx(fr, ox), fy(fr, fr[3]) + 0.5, 0]} Math(y_name, 0.8, col)
    }
    return parts
}

# tick marks with numbers at the listed data values
let XTicks = |fr, xs, col = SLATE, places = 0| block {
    let oy = fy(fr, clamp(fr[2], 0, fr[3]))
    for (x in xs) {
        . Seg([fx(fr, x), oy - 0.07, 0], [fx(fr, x), oy + 0.07, 0], col, 2)
        . center{[fx(fr, x), oy - 0.33, 0]} Math(to_string(x, places), 0.72, col)
    }
}
let YTicks = |fr, ys, col = SLATE, places = 0| block {
    let ox = fx(fr, clamp(fr[0], 0, fr[1]))
    for (y in ys) {
        . Seg([ox - 0.07, fy(fr, y), 0], [ox + 0.07, fy(fr, y), 0], col, 2)
        . center{[ox - 0.36, fy(fr, y), 0]} Math(to_string(y, places), 0.72, col)
    }
}

# faint grid lines at the listed data values
let GridLines = |fr, xs, ys, col = MIST| block {
    for (x in xs) {
        . Seg(at(fr, x, fr[2]), at(fr, x, fr[3]), col, 1.5)
    }
    for (y in ys) {
        . Seg(at(fr, fr[0], y), at(fr, fr[1], y), col, 1.5)
    }
}

# the graph of f over [a, b], drawn in the frame. values are clamped to the window
let Graph = |fr, f, a, b, col = AZURE, w = 4, n = 160|
    stroke{col, w} Polyline(map(sample(a, b, n), |x| at(fr, x, clamp(fr[2], f(x), fr[3]))))

# the region between the graph of f and the x axis over [a, b]
let AreaUnder = |fr, f, a, b, col = AZURE, amount = 0.2, n = 80| {
    var pts = [at(fr, a, 0)]
    for (x in sample(a, b, n)) {
        pts .= at(fr, x, clamp(fr[2], f(x), fr[3]))
    }
    pts .= at(fr, b, 0)
    return fill{tint(col, amount)} stroke{alpha{0} col, 0} Polygon(pts)
}

let rnd = |i, salt| mod(seeded_random(i, salt) + 1, 1)
let roll = |i, salt| to_int(floor(6 * rnd(i, salt))) + 1
# five waits for a bus, in minutes
let DATA = [2, 4, 4, 5, 10]
let total_dist = |xs, c| sum(map(xs, |x| abs(x - c)))
let total_sq = |xs, c| sum(map(xs, |x| (x - c) ^ 2))
# a number line on which the value v sits at x = x0 + v * u
let Ruler = |x0, u, lo, hi, step, y, name| block {
    . Seg([x0 + lo * u - 0.15, y, 0], [x0 + hi * u + 0.15, y, 0], SLATE, 2.5)
    for (v in range(lo, hi + 1, step)) {
        . Seg([x0 + v * u, y - 0.08, 0], [x0 + v * u, y + 0.08, 0], SLATE, 2)
        . center{[x0 + v * u, y - 0.32, 0]} Math(to_string(v), 0.72, SLATE)
    }
    if (name != "") {
        . center{[x0 + (lo + hi) / 2 * u, y - 0.72, 0]} Note(name, 0.72)
    }
}
# one dot per value, equal values stacked
let Stack = |xs, x0, u, y, col, r| block {
    var seen = []
    for (x in xs) {
        let below = count(seen, |v| v == x)
        . Pt([x0 + x * u, y + r + 0.04 + below * (2 * r + 0.03), 0], col, r)
        seen .= x
    }
}
# a line that is skipped when its ends coincide
let Span = |a, b, col, w| {
    if (abs(a[0] - b[0]) < 0.01) {
        return []
    }
    return Seg(a, b, col, w)
}