Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Statistics (core-lite)

Core matten provides exactly four statistics reductions (RFC-040), alongside the mean/mean_axis already in Reductions and matrix multiplication:

Anything with significant statistical policy — quantile, percentile, histogram, covariance, correlation, z-score, sample variance — is out of core scope (a possible future matten-stats companion). matten is a family-car PoC library, not a statistics package.

Population variance, not sample variance

All four use population variance (ddof = 0):

mean = sum(x) / n
var  = sum((x - mean)^2) / n
std  = sqrt(var)

There is no sample-variance (ddof = 1) variant, no var_with_ddof, and no nanvar/nanstd in core. A single-element tensor has variance 0.0. A two-pass algorithm is used (mean first, then squared deviations) to avoid the avoidable cancellation of the naive one-pass E[x^2] - E[x]^2.

NaN propagates: any NaN element yields NaN (per-slice for the axis variants), consistent with the other f64 reductions. Use try_numeric() to convert a dynamic tensor first; the statistics methods reject dynamic tensors.

var / std

Tensor::var(&self) -> f64
Tensor::std(&self) -> f64
Tensor::try_var(&self) -> Result<f64, MattenError>
Tensor::try_std(&self) -> Result<f64, MattenError>
[1, 2, 3, 4]  ->  mean 2.5,  var 1.25,  std sqrt(1.25) ≈ 1.118

The try_* forms return MattenError::Unsupported on a dynamic tensor. They also guard the empty-tensor case with MattenError::InvalidArgument (RFC-105) — reachable via slicing to a zero-sized shape and, since RFC-111, directly from any constructor as well.

var_axis / std_axis

Tensor::var_axis(&self, axis: usize) -> Tensor
Tensor::std_axis(&self, axis: usize) -> Tensor
Tensor::try_var_axis(&self, axis: usize) -> Result<Tensor, MattenError>
Tensor::try_std_axis(&self, axis: usize) -> Result<Tensor, MattenError>

The reduced axis is removed from the output shape (no keepdims), matching the existing axis reductions (mean_axis, sum_axis):

[[1, 2, 3], [4, 5, 6]]  var_axis(0)  ->  [2.25, 2.25, 2.25]   // shape [3], per column
[[1, 2, 3], [4, 5, 6]]  var_axis(1)  ->  [2/3, 2/3]            // shape [2], per row

The shape behavior is the same for var_axis and std_axis; std_axis just takes the square root of each variance:

input shape [2, 3]
axes         0  1

var_axis(0): collapse rows, keep columns

[ 1  2  3 ]
[ 4  5  6 ]  ->  [ var([1,4])  var([2,5])  var([3,6]) ]
             ->  [    2.25        2.25        2.25    ]   shape [3]

var_axis(1): collapse columns, keep rows

[ 1  2  3 ]
[ 4  5  6 ]  ->  [ var([1,2,3])  var([4,5,6]) ]
             ->  [     2/3            2/3     ]           shape [2]

The try_* forms return MattenError::Shape if axis >= rank, or MattenError::Unsupported on a dynamic tensor.

Empty reduced axis (RFC-110): var_axis/try_var_axis and std_axis/try_std_axis additionally return MattenError::InvalidArgument (or, for the panicking forms, a panic carrying that message) when the reduced axis has length 0 — variance and standard deviation of nothing are undefined, not NaN. A zero-length axis that survives the reduction (a different axis than the one being reduced) is unaffected and still returns Ok with an empty result.

Out of scope for core

sample variance (ddof = 1)    quantile        percentile
histogram                     covariance      correlation
z-score                       nanvar/nanstd   statistical tests

These stay out of core; RFC-040 §9’s gate for a future companion was cleared once at least three clearly-useful, well-scoped APIs were accepted, and that companion — matten-stats — now exists (RFC-078, expanded by RFC-083; see below). Some (z-score) overlap with matten-mlprep and must not be duplicated there.

The matten-stats companion (RFC-078, RFC-083, RFC-084, RFC-090)

matten-stats is a separate, production-ready candidate companion crate (promoted in RFC-084 once its surface settled) computing statistical summaries that core deliberately excludes: covariance, covariance_population, correlation, quantile, skewness, kurtosis, and histogram. A summary is returned as f64 where it is scalar, and as a small owned struct (Histogram) where it is inherently vector-valued — matten-stats never returns a Tensor (RFC-090 §5 amended the original Tensor -> f64 framing to make room for histogram’s non-scalar result). Its estimator conventions differ per function, matching what each function’s name is expected to mean in the wider ecosystem (NumPy/SciPy/R/pandas):

covariance             sample,     ddof = 1        (NumPy/R/pandas `cov`/`corrcoef` default)
covariance_population  population, ddof = 0        (explicit in the name; no default to choose)
correlation            ddof-invariant                 (the n - 1 factors cancel)
skewness               g1,  uncorrected                (SciPy `skew(bias=True)` default; NOT pandas' `.skew()`)
kurtosis               g2,  uncorrected, EXCESS         (SciPy `kurtosis(fisher=True, bias=True)` default; NOT pandas' `.kurt()`)

kurtosis reports excess kurtosis (Fisher’s definition): a normal distribution scores 0.0, not 3.0. pandas’ .skew()/.kurt() bias-correct and so return a different number than skewness/kurtosis for the same input — this divergence is deliberate, not an oversight. See the crate’s own README for the full API and error model.

histogram — bin count is the caller’s choice (RFC-090)

pub struct Histogram {
    pub counts: Vec<usize>,
    pub edges: Vec<f64>,
}

pub fn histogram(x: &Tensor, bins: usize) -> Result<Histogram, MattenStatsError>;

There is no automatic bin-count rule — not Sturges, not Freedman-Diaconis, not Scott, no "auto". bins is required because bin count is a genuine analytical choice; a histogram whose bin count was picked for the caller teaches the wrong lesson (RFC-090 §4.1).

The range is always [min(x), max(x)]; there is no range parameter. The last bin is closed at the top ([edges[bins - 1], edges[bins]]), unlike every other bin ([edges[i], edges[i + 1]), half-open) — otherwise the maximum value would fall in no bin and silently vanish from the counts. A constant input errors (ZeroVariance) rather than widening the range the way NumPy does ((v - 0.5, v + 0.5)); that 0.5 comes from nowhere in the data.

Example

See 16_variance_std.rs for a runnable walkthrough of core’s var/std, and histogram.rs (cargo run -p matten-stats --example stats_histogram) for matten-stats’s histogram.