A Means to an End
in Data Science
Mathityahu’s uncle Shimon called one evening with a business question.
“I have data,” said Shimon. “I need the average. What’s the average?”
“Which average?” said Mathityahu.
A long pause. “There’s more than one?”
There are three. And choosing the wrong one is like asking the wrong rabbi — you’ll get a confident answer that leads you completely astray.
The three classical means are called the Pythagorean Means: the Arithmetic mean, the Geometric mean, and the Harmonic mean. Each one is correct. Each one answers a different question. The art is knowing which question you’re actually asking.
But first — what is an estimator?
An estimator is a rule for producing an answer about a whole population from a sample of it.
Shimon has a sample. He wants to say something about the full population. The mean is his estimator — but which mean he should use depends on the physics of his problem, not convention.
Let’s look at three examples. In each one, the wrong mean gives a confidently wrong answer.
Example 1 — Particles Traveling a Fixed Distance
Particle i travels a fixed distance x at velocity vᵢ, taking time tᵢ = x / vᵢ.
We have a sample of the velocities. We want to estimate the total distance all particles traveled — using an estimated mean velocity v̄ applied to the total time.
We need v̄ such that:
Since all particles travel the same distance x, we substitute tᵢ = x / vᵢ:
Solving for v̄:
That is the Harmonic mean. Not the arithmetic mean. The arithmetic mean will give you an answer that sounds reasonable and is wrong.
import numpy as np
from scipy.stats import gmean, hmean
np.random.seed(42)
n = 50
mu, sigma = 100, 40
t_i = sigma * np.random.randn(n) + mu # random travel times
x_i = 1000 * np.ones(n) # fixed distance
v_i = x_i / t_i # derived velocities
# Sample 70% and estimate mean velocity
sample_idx = np.random.choice(n, int(0.7 * n), replace=False)
sampled_v = v_i[sample_idx]
h_mean = hmean(sampled_v)
a_mean = np.mean(sampled_v)
g_mean = gmean(sampled_v)
# Estimate total distance using each mean
pred_harmonic = h_mean * np.sum(t_i)
pred_arithmetic = a_mean * np.sum(t_i)
pred_geometric = g_mean * np.sum(t_i)
true_total = np.sum(x_i)
The Harmonic mean lands on the true total. The others are off — not wildly, but consistently. In a business context, “consistently off” compounds into real money.
“Why does the harmonic mean win here?” asked Shimon.
Because time is the denominator. Fast particles spend less time; slow ones spend more. A straight arithmetic average of velocities overweights the fast ones relative to how much time they actually consumed. The harmonic mean corrects for this.
Example 2 — Particles Traveling a Fixed Duration
Same setup — but now each particle travels for a fixed time t and covers whatever distance it can at its velocity.
We want the same thing: estimate total distance from a sample of velocities.
This time tᵢ = t for all particles, so:
That is the Arithmetic mean. The simplest one. When everyone spends the same amount of time doing something, a straight average is exactly right.
np.random.seed(42)
n = 30
mu, sigma = 100, 30
x_i = sigma * np.random.randn(n) + mu # random distances
t_i = 10 * np.ones(n) # fixed duration
v_i = x_i / t_i
sample_idx = np.random.choice(n, int(0.7 * n), replace=False)
sampled_v = v_i[sample_idx]
h_mean = hmean(np.abs(sampled_v))
a_mean = np.mean(sampled_v)
g_mean = gmean(np.abs(sampled_v))
pred_arithmetic = a_mean * np.sum(t_i)
pred_harmonic = h_mean * np.sum(t_i)
pred_geometric = g_mean * np.sum(t_i)
true_total = np.sum(x_i)
Arithmetic mean, dead on. The others drift.
“So when do I use arithmetic?” asked Shimon.
When the thing being averaged is directly additive — when you’re summing quantities that each contribute equally to the total. Which is most of the time, but not all of the time.
Example 3 — Population Growth
A population of, let’s say, a Jewish family — starts at size P₀ and grows by a factor αᵢ at each time step.
We have a sample of the growth rates αᵢ. We want to estimate the population at time t using a single constant growth rate ᾱ:
That is the Geometric mean — the t-th root of the product of all growth rates.
This makes intuitive sense: when quantities multiply over time rather than add, the geometric mean is the right “average multiplier.”
np.random.seed(42)
t = 150
mu, sigma = 1.2, 0.1
alpha_i = sigma * np.random.randn(t) + mu # growth rates
sample_idx = np.random.choice(t, int(0.7 * t), replace=False)
sampled_alpha = alpha_i[sample_idx]
g_mean = gmean(sampled_alpha)
a_mean = np.mean(sampled_alpha)
h_mean = hmean(sampled_alpha)
initial_population = 10
time_steps = np.arange(t + 1)
actual = initial_population * np.cumprod(np.concatenate(([1], alpha_i)))
est_geometric = initial_population * g_mean ** time_steps
est_arithmetic = initial_population * a_mean ** time_steps
est_harmonic = initial_population * h_mean ** time_steps
The geometric mean tracks the actual population. The arithmetic mean drifts upward — it overestimates because it doesn’t account for the compounding effect of below-average years. The harmonic mean drifts the other way.
“This is the one for compound interest?” asked Shimon.
“Yes,” said Mathityahu. “And for investment returns. And for population growth. Anything that compounds.”
“My accountant uses arithmetic.”
“Your accountant is using the wrong rabbi.”
So Which One Do You Use?
| The situation | The right mean |
|---|---|
| Averaging rates where the denominator is fixed (speed over fixed distance) | Harmonic |
| Averaging quantities that add directly (speed over fixed time) | Arithmetic |
| Averaging rates that multiply over time (growth, returns, ratios) | Geometric |
The arithmetic mean is not the default correct answer. It is the correct answer for a specific class of problem — one where values add linearly and all observations contribute equally.
Before you average anything, ask: what is the physics of this problem? Is the thing you’re estimating a sum, a product, or a rate? The answer tells you which mean to use.
Or call Mathityahu. He’ll tell you the same thing, but with more footnotes.
All code for this post is available in the Jewpyter notebook repository.