The OpenD Programming Language

variance

Calculates the variance of the input

By default, if F is not floating point type or complex type, then the result will have a double type if F is implicitly convertible to a floating point type or a type for which isComplex!F is true.

Parameters

varianceAlgo

algorithm for calculating variance (default: VarianceAlgo.hybrid)

summation

algorithm for calculating sums (default: Summation.appropriate)

Return Value

The variance of the input, must be floating point or complex type

Examples

import mir.math.common: approxEqual;
import mir.complex.math: capproxEqual = approxEqual;
import mir.ndslice.slice: sliced;
import mir.complex;
alias C = Complex!double;

assert(variance([1.0, 2, 3]).approxEqual(2.0 / 2));
assert(variance([1.0, 2, 3], true).approxEqual(2.0 / 3));

assert(variance([C(1, 3), C(2), C(3)]).capproxEqual(C(-4, -6) / 2));

assert(variance!float([0, 1, 2, 3, 4, 5].sliced(3, 2)).approxEqual(17.5 / 5));

static assert(is(typeof(variance!float([1, 2, 3])) == float));

Variance of vector

import mir.math.common: approxEqual;
import mir.ndslice.slice: sliced;

auto x = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
          2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;

assert(x.variance.approxEqual(54.76562 / 11));

Variance of matrix

import mir.math.common: approxEqual;
import mir.ndslice.fuse: fuse;

auto x = [
    [0.0, 1.0, 1.5, 2.0, 3.5, 4.25],
    [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]
].fuse;

assert(x.variance.approxEqual(54.76562 / 11));

Column variance of matrix

import mir.algorithm.iteration: all;
import mir.math.common: approxEqual;
import mir.ndslice.fuse: fuse;
import mir.ndslice.topology: alongDim, byDim, map;

auto x = [
    [0.0,  1.0, 1.5, 2.0], 
    [3.5, 4.25, 2.0, 7.5],
    [5.0,  1.0, 1.5, 0.0]
].fuse;
auto result = [13.16667 / 2, 7.041667 / 2, 0.1666667 / 2, 30.16667 / 2];

// Use byDim or alongDim with map to compute variance of row/column.
assert(x.byDim!1.map!variance.all!approxEqual(result));
assert(x.alongDim!0.map!variance.all!approxEqual(result));

// FIXME
// Without using map, computes the variance of the whole slice
// assert(x.byDim!1.variance == x.sliced.variance);
// assert(x.alongDim!0.variance == x.sliced.variance);

Can also set algorithm type

import mir.math.common: approxEqual;
import mir.ndslice.slice: sliced;

auto a = [0.0, 1.0, 1.5, 2.0, 3.5, 4.25,
          2.0, 7.5, 5.0, 1.0, 1.5, 0.0].sliced;

auto x = a + 1_000_000_000;

auto y = x.variance;
assert(y.approxEqual(54.76562 / 11));

// The naive algorithm is numerically unstable in this case
auto z0 = x.variance!"naive";
assert(!z0.approxEqual(y));

auto z1 = x.variance!"online";
assert(z1.approxEqual(54.76562 / 11));

// But the two-pass algorithm provides a consistent answer
auto z2 = x.variance!"twoPass";
assert(z2.approxEqual(y));

// And the assumeZeroMean algorithm is way off
auto z3 = x.variance!"assumeZeroMean";
assert(z3.approxEqual(1.2e19 / 11));

Can also set algorithm or output type

import mir.math.common: approxEqual;
import mir.ndslice.slice: sliced;
import mir.ndslice.topology: repeat;

//Set population variance, variance algorithm, sum algorithm or output type

auto a = [1.0, 1e100, 1, -1e100].sliced;
auto x = a * 10_000;

/++
Due to Floating Point precision, when centering `x`, subtracting the mean 
from the second and fourth numbers has no effect. Further, after centering 
and squaring `x`, the first and third numbers in the slice have precision 
too low to be included in the centered sum of squares. 
+/
assert(x.variance(false).approxEqual(2.0e208 / 3));
assert(x.variance(true).approxEqual(2.0e208 / 4));

assert(x.variance!("online").approxEqual(2.0e208 / 3));
assert(x.variance!("online", "kbn").approxEqual(2.0e208 / 3));
assert(x.variance!("online", "kb2").approxEqual(2.0e208 / 3));
assert(x.variance!("online", "precise").approxEqual(2.0e208 / 3));
assert(x.variance!(double, "online", "precise").approxEqual(2.0e208 / 3));
assert(x.variance!(double, "online", "precise")(true).approxEqual(2.0e208 / 4));

auto y = uint.max.repeat(3);
auto z = y.variance!ulong;
assert(z == 0.0);
static assert(is(typeof(z) == double));

For integral slices, pass output type as template parameter to ensure output type is correct.

import mir.math.common: approxEqual;
import mir.ndslice.slice: sliced;

auto x = [0, 1, 1, 2, 4, 4,
          2, 7, 5, 1, 2, 0].sliced;

auto y = x.variance;
assert(y.approxEqual(50.91667 / 11));
static assert(is(typeof(y) == double));

assert(x.variance!float.approxEqual(50.91667 / 11));

Variance works for complex numbers and other user-defined types (provided they can be converted to a floating point or complex type)

import mir.complex.math: approxEqual;
import mir.ndslice.slice: sliced;
import mir.complex;
alias C = Complex!double;

auto x = [C(1, 2), C(2, 3), C(3, 4), C(4, 5)].sliced;
assert(x.variance.approxEqual((C(0, 10)) / 3));

Compute variance along specified dimention of tensors

import mir.algorithm.iteration: all;
import mir.math.common: approxEqual;
import mir.ndslice.fuse: fuse;
import mir.ndslice.topology: as, iota, alongDim, map, repeat;

auto x = [
    [0.0, 1, 2],
    [3.0, 4, 5]
].fuse;

assert(x.variance.approxEqual(17.5 / 5));

auto m0 = [4.5, 4.5, 4.5];
assert(x.alongDim!0.map!variance.all!approxEqual(m0));
assert(x.alongDim!(-2).map!variance.all!approxEqual(m0));

auto m1 = [1.0, 1.0];
assert(x.alongDim!1.map!variance.all!approxEqual(m1));
assert(x.alongDim!(-1).map!variance.all!approxEqual(m1));

assert(iota(2, 3, 4, 5).as!double.alongDim!0.map!variance.all!approxEqual(repeat(3600.0 / 2, 3, 4, 5)));

Arbitrary variance

assert(variance(1.0, 2, 3) == 1.0);
assert(variance!float(1, 2, 3) == 1f);

Meta