Home Tutorials Standard Library

Standard Library

Python's statistics Module: Mean, Median, and Variance Without NumPy

Pyford Notes July 6, 2026 7 min read
Key points
  • statistics ships in the standard library and needs no install — reach for it before adding a NumPy dependency for a one-off average.
  • variance()/stdev() assume the data is a sample and divide by n−1; pvariance()/pstdev() assume the full population and divide by n — picking the wrong pair silently skews the result.
  • median() averages the two middle values for even-length data; median_low()/median_high() instead pick an actual data point, which matters when the values aren't numeric-only.
  • For large datasets or anything beyond simple descriptive stats, NumPy and pandas are dramatically faster and more capable — statistics is for correctness and convenience on modest data, not performance.

mean, median, and mode

The three headline measures of central tendency are each a single function call:

import statistics as stats

data = [4, 8, 6, 5, 3, 8, 9, 8]

stats.mean(data)     # 6.375
stats.median(data)    # 6.5
stats.mode(data)       # 8 -- the most common value

mean() returns a plain average; for even-length data, median() returns the mean of the two middle values, which can be a value that never actually appears in the data (as with 6.5 above). mode() returns the single most common value and, since Python 3.8, returns the first one encountered if there's a tie, rather than raising an error the way earlier versions did — a behaviour change worth knowing if code targets multiple Python versions.

Sample vs population: variance and stdev

This is the distinction most people get wrong the first time, because both pairs of functions look interchangeable at a glance:

stats.variance(data)     # sample variance, divides by (n - 1)
stats.pvariance(data)     # population variance, divides by n
stats.stdev(data)          # sample standard deviation
stats.pstdev(data)         # population standard deviation

The rule: if your data is the entire population you care about (every student in one specific class, say), use the population versions. If your data is a sample drawn from a larger population you're trying to draw conclusions about (a survey of 200 people standing in for a country), use the sample versions — dividing by n−1 instead of n corrects for the fact that a sample's variance tends to slightly underestimate the true population variance. Using the wrong pair doesn't raise an error; it just quietly produces a number that's biased, which is worse than crashing because nothing flags it.

multimode and quantiles

Two additions from more recent Python versions cover cases mode() and manual bucketing don't handle well:

stats.multimode([1, 1, 2, 2, 3])     # [1, 2] -- all values tied for most common
stats.quantiles(data, n=4)             # quartile boundaries

multimode() returns every value tied for the highest frequency, in first-encountered order, instead of arbitrarily picking one the way mode() does. quantiles(data, n=4) splits the distribution into four equal-probability groups and returns the three cut points between them (quartiles); passing n=100 gives percentile cut points instead. The method keyword argument controls which of several standard interpolation conventions is used, which matters if results need to match a specific textbook or another tool's definition exactly.

Working with Decimal and Fraction input

Unlike hand-rolled averaging with sum(data) / len(data), statistics functions preserve the input type where it makes sense:

from decimal import Decimal
stats.mean([Decimal("1.1"), Decimal("2.2"), Decimal("3.3")])   # Decimal('2.2')

Feeding in Decimal values keeps the result as a Decimal rather than silently converting to float and reintroducing the rounding error Decimal was chosen to avoid in the first place — genuinely useful for financial or accounting figures where every digit needs to be exact.

When statistics isn't enough

statistics is deliberately modest in scope: descriptive statistics on in-memory Python sequences, computed correctly, with no dependencies. It's not built for speed — each function walks the data in pure Python, so on datasets in the millions of rows, NumPy's vectorised, compiled implementations are an order of magnitude faster or more. It also doesn't cover correlation, regression, hypothesis testing, or anything resembling a dataframe. The statistics module documentation is explicit that this is a calculator-replacement tool for small-to-modest datasets, not a data-science library — reach for pandas or NumPy once the dataset or the analysis outgrows that description.