Hands-on statistics vs manual mean/median/stdev lab: real ops/s for fmean, sum/len, and sorted mid versus the stdlib, measured on Linux localhost (lab).
4 min read
How expensive is statistics.mean / median / stdev versus a hand sum/len, statistics.fmean, or a sorted mid? This lab times numpy-free reductions on float lists on Linux localhost.
Related links:
Lab honesty (1 Oct 2026 IST): Python 3.13.5. No NumPy. Correctness checks vs manual helpers: {'mean_close': True, 'fmean_close': True, 'median_close': True, 'pstdev_close': True}. Affiliates: 0. statistics.mean favors numeric care over micro-speed; fmean is the float fast path.
Verdict up front (N=10 000): sum/len ~209M elem/s vs statistics.mean ~3.4M (~62×); fmean ~24× statistics.mean. Median: manual ≈ statistics (~1.00×). Manual pstdev ~6.3× statistics.pstdev.
| Arm | Pattern |
|---|---|
statistics.mean | stdlib exact-ish mean |
statistics.fmean | float-optimized mean |
sum(xs)/len(xs) | manual |
| pure loop sum | interpreter loop |
statistics.median / manual sorted mid | both sort |
pstdev / stdev | stdlib vs one-pass-after-mean |
| Welford | online mean+sample stdev |
Script: lab-evidence/69-statistics-vs-manual-mean/results/run_lab.py.
| Arm | red/s | elem/s | ns/red |
|---|---|---|---|
| sum/len | 20,857 | 208.6M | 47945 |
| fmean | 8,027 | 80.3M | 124582 |
| loop sum | 4,178 | 41.8M | 239370 |
| statistics.mean | 338 | 3.4M | 2961878 |
| Arm | red/s | elem/s |
|---|---|---|
| statistics.median | 920 | 9.2M |
| manual median | 916 | 9.2M |
| manual pstdev | 1,468 | 14.7M |
| statistics.pstdev | 234 | 2.3M |
| Welford mean+stdev | 1,892 | 18.9M |
| N | speedup |
|---|---|
| 100 | 90× |
| 1 000 | 68× |
| 10 000 | 62× |
| 100 000 | 62× |
fmean closes much of the gap (~24× vs statistics.mean at N=10 k) while staying in the statistics module.
statistics.fmean or sum/len; treat statistics.mean as the careful/general API.statistics.mean’s care may matter more than ns.statistics.mean looks “slow”On floats, statistics.mean takes a more general numeric path than fmean / a bare sum. That shows up as tens of × in a microbench — fine for reports and mixed types, noisy for a per-request float gauge. If the hot path is “average of floats I already trust,” call fmean (or sum/len) and reserve mean for clarity at API boundaries. Median’s cost is the sort either way; do not expect a helper rename to erase O(n log n).
statistics.mean in a tight float telemetry loop — use fmean.stdev vs pstdev (n−1 vs n).| Need | Prefer |
|---|---|
| Fast float mean | fmean or sum/len |
| Mixed numeric types / docs clarity | statistics.mean |
| Median | statistics.median |
| Online mean+variance | Welford |
| Huge numeric arrays | NumPy (elsewhere) |
Evidence: /workspace/lab-evidence/69-statistics-vs-manual-mean/results/.
Use fmean for float speed; mean for generality. On this box N=10 k sum/len beat statistics.mean by ~62×, fmean by ~24×, while median stayed a wash and manual pstdev led by ~6×. Match the helper to whether you are optimizing a hot float path or writing clear stats code.
Lab evidence
Lab 1 Oct 2026 IST. Python 3.13.5. N=10k: sum/len mean 209M elem/s vs statistics.mean 3.4M (~62x); fmean ~24x statistics.mean; median ≈ parity; manual pstdev ~6.3x. Affiliates: 0. Evidence: lab-evidence/69-statistics-vs-manual-mean/.
Hands-on platform.platform vs os.uname host inventory lab: real ops/s plus cache notes, measured on Linux localhost today in this hands-on lab for SREs.
Summary1 Oct 20264 min read
Hands-on signal SIGUSR1 vs threading.Event wakeup lab: real p50 latency in microseconds, measured on Linux localhost today in this hands-on lab for SREs.
Summary1 Oct 20264 min read
Hands-on cmath vs math.hypot magnitude ops lab: real ops/s for abs, polar, and phase, measured on Linux localhost today in this hands-on lab for SREs.
Summary1 Oct 20263 min read