The statistics module, added in Python 3.4, provides functions for calculating mathematical statistics of numeric data: measures of central tendency (mean, median, mode), measures of spread (variance, standard deviation), and tools for working with the normal distribution.
Everyday statistics, built in
What you will learn: computing averages (mean, median, mode) and measures of spread (variance, standard deviation) without needing NumPy.
How to read this tab: Pay attention to mean vs median — outliers distort the mean but not the median.
The statistics module answers everyday questions about a list of numbers: what is the average? the middle value? the most common one? how spread out are they? It is built into Python — no need to install NumPy for basic statistics.
Mean is pulled by outliers; median resists them. One billionaire makes the average net worth meaningless, but the median (middle value) still represents the typical person. Choosing the right average is a judgement about your data, not just a calculation.
Averages: mean, median, mode
import statistics
scores = [85, 90, 78, 92, 88, 90, 76]
# Mean — the arithmetic average
print(statistics.mean(scores)) # 85.57...
# Median — the middle value when sorted (robust against outliers)
print(statistics.median(scores)) # 88
# Mode — the most common value
print(statistics.mode(scores)) # 90 (appears twice)
# Why median matters: outliers distort the mean but not the median
incomes = [30000, 35000, 32000, 38000, 5000000] # one billionaire
print(statistics.mean(incomes)) # 1027000 — misleading!
print(statistics.median(incomes)) # 35000 — represents the typical value
# fmean (3.8+) — faster, always returns float
print(statistics.fmean(scores)) # 85.571... (float, faster than mean)The mean is pulled toward extreme values; one billionaire in a room makes the average net worth meaningless. The median (middle value) resists outliers and often better represents "typical." Choosing the right average is a judgement, not just a calculation.
Sample vs population: use stdev/variance for a sample, pstdev/pvariance for a full population. The sample versions divide by n−1 to correct for sampling bias. The common case — data drawn from a larger group — wants the sample versions.
Variance and standard deviation
import statistics
data = [2, 4, 4, 4, 5, 5, 7, 9]
# Standard deviation — how spread out the data is, in original units
print(statistics.stdev(data)) # 2.13... (sample std dev)
print(statistics.pstdev(data)) # 2.0 (population std dev)
# Variance — the square of standard deviation
print(statistics.variance(data)) # 4.57... (sample variance)
print(statistics.pvariance(data)) # 4.0 (population variance)
# Sample vs population:
# - Use stdev/variance (sample) when data is a SAMPLE of a larger group
# - Use pstdev/pvariance (population) when data is the ENTIRE group
# The sample versions divide by (n-1), correcting for sampling bias.✅ Beginner tab complete
- I can compute mean, median, and mode
- I know median resists outliers while mean does not
- I can compute standard deviation and variance
- I know sample (stdev) vs population (pstdev) versions
Normal distribution and precision
What you will learn: the NormalDist class for probabilities and percentiles, and how statistics preserves numeric types like Decimal and Fraction.
How to read this tab: Try NormalDist with a real example (IQ scores) — cdf and inv_cdf answer practical probability questions.
NormalDist answers real probability questions. Model data with NormalDist(mu, sigma), then cdf(x) gives the fraction below x, and inv_cdf(p) gives the value at percentile p. from_samples() builds a distribution directly from data.
The normal distribution
Python 3.8+ includes a NormalDist class for working with normal (Gaussian) distributions — computing probabilities, percentiles, and modelling data.
from statistics import NormalDist
# Model IQ scores: mean 100, standard deviation 15
iq = NormalDist(mu=100, sigma=15)
# What fraction of people score below 130? (cumulative distribution)
print(iq.cdf(130)) # 0.977... — about 97.7%
# What IQ is the 90th percentile? (inverse CDF)
print(iq.inv_cdf(0.90)) # 119.2...
# Probability density at a point
print(iq.pdf(100)) # 0.0266... (peak, at the mean)
# Build a NormalDist directly from data
data = [88, 92, 95, 100, 105, 108, 112]
dist = NormalDist.from_samples(data)
print(f"mean={dist.mean:.1f}, stdev={dist.stdev:.1f}")
# Arithmetic on distributions (sum of independent normals)
combined = NormalDist(100, 15) + NormalDist(50, 8)
print(combined.mean, round(combined.stdev, 2)) # 150 17.0
# Overlap between two distributions (how similar they are)
a = NormalDist(100, 15)
b = NormalDist(110, 15)
print(a.overlap(b)) # 0.737... — 73.7% overlapstatistics preserves your numeric type. Give it Decimal and you get Decimal back — exact, which matters for money. Give it Fraction and you get Fraction. This type preservation is a key advantage over float-only tools for exact computation.
Precision and data types
import statistics
from fractions import Fraction
from decimal import Decimal
# statistics preserves the input type where it can
print(statistics.mean([1, 2, 3])) # 2 (int average happens to be int)
print(statistics.mean([1, 2])) # 1.5 (float when needed)
print(type(statistics.mean([Decimal("1.1"), Decimal("2.2")]))) # Decimal
print(statistics.mean([Fraction(1, 3), Fraction(2, 3)])) # Fraction(1, 2)
# This matters for money — Decimal preserves exact precision
prices = [Decimal("19.99"), Decimal("5.49"), Decimal("12.00")]
print(statistics.mean(prices)) # Decimal('12.49333...')
# quantiles — divide data into equal-probability intervals
data = list(range(1, 101)) # 1 to 100
print(statistics.quantiles(data, n=4)) # quartiles: [25.75, 50.5, 75.25]
print(statistics.quantiles(data, n=10)) # deciles
# median variants for edge cases
print(statistics.median_low([1, 2, 3, 4])) # 2 (lower of two middles)
print(statistics.median_high([1, 2, 3, 4])) # 3 (higher of two middles)stdev/variance when your data is a sample drawn from a larger population (the common case). Use pstdev/pvariance only when your data is the complete population. The sample versions divide by n−1 to correct for bias.✅ Intermediate tab complete
- I can use NormalDist for cdf, inv_cdf, and from_samples
- I know statistics preserves Decimal and Fraction input types
- I can compute quantiles (quartiles, deciles)
- I know median_low and median_high for even-length data
Accuracy, regression, and leaving the stdlib
What you will learn: how statistics prioritises numerical accuracy, correlation/covariance/linear_regression (3.10+), and when to move to NumPy/SciPy/pandas.
How to read this tab: Read when you need correlation or regression, or when deciding whether to graduate to the scientific stack.
Numerical accuracy, mean implementations, and when to leave the stdlib
A distinguishing feature of the statistics module is its emphasis on correctness over speed. statistics.mean avoids the floating-point error that naive summation accumulates, and it preserves the input's numeric type — returning Fraction for Fraction input and Decimal for Decimal input, which matters for exact financial or rational computation. The faster fmean always converts to float, trading that type preservation and some accuracy for speed.
import statistics
# statistics.mean is accurate even for values that defeat naive summation
data = [1e20, 1, -1e20] * 1000
print(sum(data) / len(data)) # may show float error
print(statistics.mean(data)) # accurate
# correlation, covariance, and linear regression (3.10+)
xs = [1, 2, 3, 4, 5]
ys = [2, 4, 5, 4, 6]
print(statistics.correlation(xs, ys)) # Pearson correlation coefficient
print(statistics.covariance(xs, ys)) # sample covariance
# linear_regression returns slope and intercept (3.10+)
slope, intercept = statistics.linear_regression(xs, ys)
print(f"y = {slope:.2f}x + {intercept:.2f}")
# Predict a new value
predict = lambda x: slope * x + intercept
print(predict(6))
# geometric_mean and harmonic_mean for the right kinds of data
print(statistics.geometric_mean([1.1, 1.2, 1.5])) # for growth rates
print(statistics.harmonic_mean([40, 60])) # for rates/speeds -> 48.0
# Multimodal data — mode returns first; multimode returns all
print(statistics.multimode([1, 1, 2, 2, 3])) # [1, 2]The module deliberately covers descriptive statistics and basic inferential tools (correlation, covariance, simple linear regression as of 3.10) but stops short of the full statistical toolkit. For hypothesis testing, multiple regression, ANOVA, time-series analysis, or anything involving large datasets and performance, the ecosystem standard is the scientific stack: NumPy for arrays, SciPy for statistical tests and distributions, pandas for tabular data, and statsmodels for advanced modelling. The standard library statistics module fills the gap where you need a correct mean, median, standard deviation, or a normal-distribution calculation without pulling in those dependencies.
✅ Expert tab complete
- I know statistics.mean is accurate where naive summation fails
- I can compute correlation, covariance, and linear_regression (3.10+)
- I know geometric_mean and harmonic_mean for the right data
- I know to use NumPy/SciPy/pandas for large data or advanced stats