Two complementary APIs cover the same ground:
| Batch | Streaming | |
|---|---|---|
| Entry point | summarize | RunningStats |
| Input | the whole series | one value at a time |
| Memory | ||
| Gives you | mean, variance, extrema, median | mean, variance, extrema |
The median is the reason the streaming API is not a drop-in replacement: order statistics cannot be maintained exactly in constant space.
Definitions¶
For observations :
where is ddof. Use (the default) when the series is a
sample from a larger population, and when it is the
population.
Why is the default ddof=1 rather than 0?
With the estimator is biased low: it divides by but the deviations are taken from the sample mean, which is itself fitted to the data and therefore sits closer to the points than the true mean does. Bessel’s correction () compensates exactly. NumPy defaults to ; pandas and R default to . This package follows pandas, because “I have a sample” is the more common situation.
Quantiles¶
quantile uses linear interpolation between order
statistics — the same as NumPy’s default method="linear". With
and :
import mypackage as mp
data = [1.0, 2.0, 3.0, 4.0]
mp.quantile(data, 0.0) # 1.0
mp.quantile(data, 0.25) # 1.75
mp.quantile(data, 0.5) # 2.5
mp.quantile(data, 1.0) # 4.0The input is sorted internally, so order doesn’t matter, and the original list is never touched.
Standardising¶
zscores is the functional form; the stateful
Standardize transform is the one you want when the
statistics must be learned once and reused — see
Transforms.
mp.zscores([1.0, 2.0, 3.0]) # [-1.0, 0.0, 1.0]Streaming with Welford’s algorithm¶
RunningStats folds each observation into a running
mean and a running sum of squared deviations:
with .
stats = mp.RunningStats()
stats.update_many([1.0, 2.0, 3.0, 4.0])
stats.count # 4
stats.mean # 2.5
stats.std # 1.2909944487358056
stats.minimum # 1.0Updates are chainable, and the accumulator can be fed indefinitely:
stats = mp.RunningStats(ddof=0).update(1.0).update(2.0).update(3.0)Numerical stability¶
This is the reason both APIs avoid the textbook “sum of squares” formula. Consider four values offset by 109:
offset = 1e9
values = [offset + d for d in (1.0, 2.0, 3.0, 4.0)]
mp.summarize(values).variance # 1.6666666666666667
mp.RunningStats().update_many(values).variance # 1.6666666666666667Both give the exact answer . The naive alternative does not:
n = len(values)
naive = (sum(v * v for v in values) - sum(values) ** 2 / n) / (n - 1)
# -21845.333333333332 ← catastrophic cancellation, and negativeAPI¶
Full signatures live in the statistics API reference.