Statistics¶
Descriptive statistics for one-dimensional series — batch and streaming.
See the statistics guide for the maths and the numerical-stability discussion.
stats
¶
Descriptive statistics for one-dimensional series.
Two complementary styles live side by side:
- Batch, pure functions —
summarize,quantile,zscorestake a whole series and return a value. No hidden state, no mutation. - Streaming, stateful —
RunningStatsconsumes observations one at a time in constant memory using Welford's algorithm.
For a series x₁, …, x_N the sample mean x̄ and variance s² are
x̄ = (1/N) ∑ᵢ xᵢ,
s² = ∑ᵢ (xᵢ − x̄)² / (N − δ),
where δ is ddof (1 for the unbiased sample variance, 0 for the
population variance).
Summary
dataclass
¶
Summary(count: int, mean: float, variance: float, minimum: float, maximum: float, median: float, ddof: int = 1)
An immutable snapshot of a series' descriptive statistics.
Instances are frozen, so a summary can be cached, compared, or used as a fixture without any risk of being mutated downstream.
Attributes:
| Name | Type | Description |
|---|---|---|
count |
int
|
Number of observations. |
mean |
float
|
Arithmetic mean. |
variance |
float
|
Sample variance with the |
minimum |
float
|
Smallest observation. |
maximum |
float
|
Largest observation. |
median |
float
|
50th percentile, linearly interpolated. |
ddof |
int
|
Delta degrees of freedom used for |
Examples:
>>> summary = summarize([1.0, 2.0, 3.0, 4.0])
>>> summary.count, summary.mean, summary.median
(4, 2.5, 2.5)
>>> round(summary.std, 4)
1.291
>>> summary.spread
3.0
to_dict
¶
Return the summary as a plain dictionary.
Derived properties (std, spread) are included so the result is
directly serialisable to JSON or renderable as a table.
Returns:
| Type | Description |
|---|---|
dict[str, float]
|
A dictionary of every field plus |
Examples:
Source code in src/mypackage/stats.py
RunningStats
dataclass
¶
RunningStats(ddof: int = 1, _count: int = 0, _mean: float = 0.0, _m2: float = 0.0, _minimum: float = inf, _maximum: float = -inf)
Streaming mean and variance via Welford's online algorithm.
Memory is constant regardless of how many observations are consumed, and the update is numerically stable — unlike the naive "sum of squares minus square of sums" formulation, which loses precision catastrophically for large means.
Each observation xₙ updates the running mean x̄ₙ and the sum of squared deviations M₂,ₙ:
x̄ₙ = x̄ₙ₋₁ + (xₙ − x̄ₙ₋₁) / n,
M₂,ₙ = M₂,ₙ₋₁ + (xₙ − x̄ₙ₋₁) (xₙ − x̄ₙ).
Attributes:
| Name | Type | Description |
|---|---|---|
ddof |
int
|
Delta degrees of freedom used by |
Raises:
| Type | Description |
|---|---|
ValidationError
|
If |
Examples:
>>> stats = RunningStats()
>>> stats.update_many([1.0, 2.0, 3.0, 4.0]).mean
2.5
>>> round(stats.std, 4)
1.291
>>> stats.count, stats.minimum, stats.maximum
(4, 1.0, 4.0)
The streaming result agrees with the batch one:
>>> data = [0.5, -1.25, 3.0, 7.75]
>>> RunningStats().update_many(data).mean == summarize(data).mean
True
minimum
property
¶
Smallest observation seen so far.
Raises:
| Type | Description |
|---|---|
EmptySeriesError
|
If no observation has been consumed. |
maximum
property
¶
Largest observation seen so far.
Raises:
| Type | Description |
|---|---|
EmptySeriesError
|
If no observation has been consumed. |
update
¶
update(value: float) -> RunningStats
Consume a single observation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value
|
float
|
The observation to fold into the running statistics. |
required |
Returns:
| Type | Description |
|---|---|
RunningStats
|
|
Raises:
| Type | Description |
|---|---|
ValidationError
|
If |
Source code in src/mypackage/stats.py
update_many
¶
update_many(series: Series) -> RunningStats
Consume every observation in series.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
series
|
Series
|
The observations to fold in, in order. |
required |
Returns:
| Type | Description |
|---|---|
RunningStats
|
|
Source code in src/mypackage/stats.py
summarize
¶
Compute descriptive statistics for series in a single pass.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
series
|
Series
|
The observations to summarise. |
required |
ddof
|
int
|
Delta degrees of freedom for the variance. Use |
1
|
Returns:
| Type | Description |
|---|---|
Summary
|
A frozen |
Raises:
| Type | Description |
|---|---|
EmptySeriesError
|
If |
ValidationError
|
If |
Examples:
>>> summary = summarize([2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0], ddof=0)
>>> summary.count, summary.mean, summary.variance
(8, 5.0, 4.0)
>>> summary.minimum, summary.median, summary.maximum
(2.0, 4.5, 9.0)
A single observation has no unbiased variance, so use ddof=0:
The mean survives inputs whose sum would overflow:
Source code in src/mypackage/stats.py
quantile
¶
quantile(series: Series, q: float) -> float
Return the q-th quantile using linear interpolation.
With sorted observations x_(1) ≤ … ≤ x_(N) and the fractional position h = q (N − 1), the result is
Q(q) = x_(j+1) + (h − j) (x_(j+2) − x_(j+1)), with j = ⌊h⌋.
This matches NumPy's default method="linear".
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
series
|
Series
|
The observations. |
required |
q
|
float
|
Quantile level in |
required |
Returns:
| Type | Description |
|---|---|
float
|
The interpolated quantile. |
Raises:
| Type | Description |
|---|---|
EmptySeriesError
|
If |
ValidationError
|
If |
Examples:
>>> data = [1.0, 2.0, 3.0, 4.0]
>>> quantile(data, 0.0), quantile(data, 0.5), quantile(data, 1.0)
(1.0, 2.5, 4.0)
>>> round(quantile(data, 0.25), 4)
1.75
Source code in src/mypackage/stats.py
zscores
¶
zscores(series: Series, *, ddof: int = 1) -> list[float]
Standardise series to zero mean and unit variance.
zᵢ = (xᵢ − x̄) / s, with x̄ the mean and s the standard deviation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
series
|
Series
|
The observations to standardise. |
required |
ddof
|
int
|
Delta degrees of freedom for the standard deviation. |
1
|
Returns:
| Type | Description |
|---|---|
list[float]
|
A new list of standardised values. |
Raises:
| Type | Description |
|---|---|
EmptySeriesError
|
If |
ValidationError
|
If the series is constant, so |
Examples:
>>> [round(z, 4) for z in zscores([1.0, 2.0, 3.0])]
[-1.0, 0.0, 1.0]
>>> try:
... zscores([2.0, 2.0, 2.0])
... except ValidationError as err:
... print(err)
cannot standardise a constant series (std == 0)