The OpenD Programming Language

skewness

Calculates the skewness of the input

By default, if F is not floating point type, then the result will have a double type if F is implicitly convertible to a floating point type.

Parameters

skewnessAlgo

algorithm for calculating skewness (default: SkewnessAlgo.hybrid)

summation

algorithm for calculating sums (default: Summation.appropriate)

Return Value

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

Examples

Simple example

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

assert(skewness([1.0, 2, 3]).approxEqual(0.0));

assert(skewness([1.0, 2, 4]).approxEqual((2.222222 / 3) / pow(4.666667 / 2, 1.5) * (3.0 ^^ 2) / (2.0 * 1.0)));
assert(skewness([1.0, 2, 4], true).approxEqual((2.222222 / 3) / pow(4.666667 / 3, 1.5)));

assert(skewness!float([0, 1, 2, 3, 4, 6].sliced(3, 2)).approxEqual(0.462910));

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

Skewness of vector

import mir.math.common: approxEqual, pow;

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];

assert(x.skewness.approxEqual((117.005859 / 12) / pow(54.765625 / 11, 1.5) * (12.0 ^^ 2) / (11.0 * 10.0)));

Skewness of matrix

import mir.math.common: approxEqual, pow;
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.skewness.approxEqual((117.005859 / 12) / pow(54.765625 / 11, 1.5) * (12.0 ^^ 2) / (11.0 * 10.0)));

Column skewness of matrix

import mir.algorithm.iteration: all;
import mir.math.common: approxEqual, pow;
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 = [-1.090291, 1.732051, 1.732051, 1.229809];

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

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

Can also set algorithm type

import mir.math.common: approxEqual, pow, sqrt;
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 + 100_000_000_000;

// The online algorithm is numerically unstable in this case
auto y = x.skewness!"online";
assert(!y.approxEqual((117.005859 / 12) / pow(54.765625 / 11, 1.5) * (12.0 ^^ 2) / (11.0 * 10.0)));

// The naive algorithm has an assert error in this case because standard
// deviation is calculated naively as zero. The skewness formula would then
// be dividing by zero. 
//auto z0 = x.skewness!(real, "naive");

// However, the two-pass and three-pass algorithms are numerically stable in this case
auto z1 = x.skewness!"twoPass";
assert(z1.approxEqual(12.000999 / 12 * sqrt(12.0 * 11.0) / 10.0));
assert(!z1.approxEqual(y));
auto z2 = x.skewness!"threePass";
assert(z2.approxEqual((12.000999 / 12) * sqrt(12.0 * 11.0) / 10.0));
assert(!z2.approxEqual(y));

// And the assumeZeroMean algorithm provides the incorrect answer, as expected
auto z3 = x.skewness!"assumeZeroMean";
assert(!z3.approxEqual(y));

Can also set algorithm or output type

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

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

auto a = [1.0, 1e98, 1, -1e98].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.skewness(false).approxEqual(0.0));
assert(x.skewness(true).approxEqual(0.0));

assert(x.skewness!("online").approxEqual(0.0));
assert(x.skewness!("online", "kbn").approxEqual(0.0));
assert(x.skewness!("online", "kb2").approxEqual(0.0));
assert(x.skewness!("online", "precise").approxEqual(0.0));
assert(x.skewness!(double, "online", "precise").approxEqual(0.0));
assert(x.skewness!(double, "online", "precise")(true).approxEqual(0.0));

auto y = [uint.max - 2, uint.max - 1, uint.max].sliced;
auto z = y.skewness!(ulong, "threePass");
assert(z == 0.0);
static assert(is(typeof(z) == double));

For integral slices, can 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.skewness;
assert(y.approxEqual(0.925493));
static assert(is(typeof(y) == double));

assert(x.skewness!float.approxEqual(0.925493));

Skewness works for other user-defined types (provided they can be converted to a floating point)

static struct Foo {
    float x;
    alias x this;
}

Foo[] foo = [Foo(1f), Foo(2f), Foo(3f)];
assert(foo.skewness == 0f);

Compute skewness 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, 3],
    [3.0, 4, 5],
    [6.0, 7, 7],
].fuse;

assert(x.skewness.approxEqual(-0.308571));

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

auto m1 = [0.935220, 0.0, -1.732051];
assert(x.alongDim!1.map!skewness.all!approxEqual(m1));
assert(x.alongDim!(-1).map!skewness.all!approxEqual(m1));
assert(iota(3, 4, 5, 6).as!double.alongDim!0.map!skewness.all!approxEqual(repeat(0.0, 4, 5, 6)));

Arbitrary skewness

assert(skewness(1.0, 2, 3) == 0.0);
assert(skewness!float(1, 2, 3) == 0f);

See Also

Meta