Report a bug
If you spot a problem with this page, click here to create a GitHub issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

mir.stat.descriptive.weighted

This module contains algorithms for descriptive statistics with weights.
License:
Authors:
John Michael Hall
enum AssumeWeights: bool;
Assumptions used for weighted moments
primary
Primary, does not assume weights sum to one
sumToOne
Assumes weights sum to one
struct WMeanAccumulator(T, Summation summation, AssumeWeights assumeWeights, U = T, Summation weightsSummation = summation);
Output range for wmean.
Examples:
Assume weights sum to 1
import mir.math.sum: Summation;
import mir.ndslice.slice: sliced;
import mir.test: should;

WMeanAccumulator!(double, Summation.pairwise, AssumeWeights.sumToOne) x;
x.put([0.0, 1, 2, 3, 4].sliced, [0.2, 0.2, 0.2, 0.2, 0.2].sliced);
x.wmean.should == 2;
x.put(5, 0.0);
x.wmean.should == 2;
Examples:
Do not assume weights sum to 1
import mir.math.sum: Summation;
import mir.ndslice.slice: sliced;
import mir.test: shouldApprox;

WMeanAccumulator!(double, Summation.pairwise, AssumeWeights.primary) x;
x.put([0.0, 1, 2, 3, 4].sliced, [1, 2, 3, 4, 5].sliced);
x.wmean.shouldApprox == 40.0 / 15;
x.put(5, 6);
x.wmean.shouldApprox == 70.0 / 21;
Examples:
Assume no weights, like MeanAccumulator
import mir.math.sum: Summation;
import mir.ndslice.slice: sliced;
import mir.test: shouldApprox;

WMeanAccumulator!(double, Summation.pairwise, AssumeWeights.primary) x;
x.put([0.0, 1, 2, 3, 4].sliced);
x.wmean.shouldApprox == 2;
x.put(5);
x.wmean.shouldApprox == 2.5;
Summator!(T, summation) wsummator;
Summator!(U, weightsSummation) weights;
const pure nothrow @nogc @property @safe F wmean(F = T)();
const pure nothrow @nogc @property @safe F wsum(F = T)();
const pure nothrow @nogc @property @safe F weight(F = U)();
void put(Slice1, Slice2)(Slice1 s, Slice2 w)
if (isSlice!Slice1 && isSlice!Slice2);
void put(SliceLike1, SliceLike2)(SliceLike1 s, SliceLike2 w)
if (isConvertibleToSlice!SliceLike1 && !isSlice!SliceLike1 && isConvertibleToSlice!SliceLike2 && !isSlice!SliceLike2);
void put(Range)(Range r)
if (isIterable!Range && !assumeWeights);
void put(RangeA, RangeB)(RangeA r, RangeB w)
if (isInputRange!RangeA && !isConvertibleToSlice!RangeA && isInputRange!RangeB && !isConvertibleToSlice!RangeB);
void put()(T x, U w);
void put()(T x)
if (!assumeWeights);
void put(F = T, G = U)(WMeanAccumulator!(F, summation, assumeWeights, G, weightsSummation) wm)
if (!assumeWeights);
template wmean(F, Summation summation = Summation.appropriate, AssumeWeights assumeWeights = AssumeWeights.primary, G = F, Summation weightsSummation = Summation.appropriate) if (!is(F : AssumeWeights))

template wmean(Summation summation = Summation.appropriate, AssumeWeights assumeWeights = AssumeWeights.primary, Summation weightsSummation = Summation.appropriate)

template wmean(F, AssumeWeights assumeWeights, Summation summation = Summation.appropriate, G = F, Summation weightsSummation = Summation.appropriate) if (!is(F : AssumeWeights))

template wmean(F, bool assumeWeights, string summation = "appropriate", G = F, string weightsSummation = "appropriate") if (!is(F : AssumeWeights))

template wmean(bool assumeWeights, string summation = "appropriate", string weightsSummation = "appropriate")

template wmean(F, string summation, bool assumeWeights = false, G = F, string weightsSummation = "appropriate") if (!is(F : AssumeWeights))

template wmean(string summation, bool assumeWeights = false, string weightsSummation = "appropriate")

template wmean(F, string summation, G, string weightsSummation, bool assumeWeights) if (!is(F : AssumeWeights))

template wmean(string summation, string weightsSummation, bool assumeWeights = false)
Computes the weighted mean 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:
F controls type of output
summation algorithm for calculating sums (default: Summation.appropriate)
assumeWeights true if weights are assumed to add to 1 (default = AssumeWeights.primary)
G controls the type of weights
Returns:
The weighted mean of all the elements in the input, must be floating point or complex type
See Also:
Examples:
import mir.complex;
import mir.ndslice.slice: sliced;
import mir.test: should, shouldApprox;
alias C = Complex!double;

wmean([1.0, 2, 3], [1, 2, 3]).shouldApprox == (1.0 + 4.0 + 9.0) / 6;
wmean!true([1.0, 2, 3], [1.0 / 6, 2.0 / 6, 3.0 / 6]).shouldApprox == (1.0 + 4.0 + 9.0) / 6;
wmean([C(1, 3), C(2), C(3)], [1, 2, 3]).should == C(14.0 / 6, 3.0 / 6);

wmean!float([0, 1, 2, 3, 4, 5].sliced(3, 2), [1, 2, 3, 4, 5, 6].sliced(3, 2)).shouldApprox == 70.0 / 21;

static assert(is(typeof(wmean!float([1, 2, 3], [1, 2, 3])) == float));
Examples:
If weights are not provided, then behaves like mean
import mir.complex;
import mir.ndslice.slice: sliced;
import mir.test: should;
alias C = Complex!double;

wmean([1.0, 2, 3]).should == 2;
wmean([C(1, 3), C(2), C(3)]).should == C(2, 1);

wmean!float([0, 1, 2, 3, 4, 5].sliced(3, 2)).should == 2.5;

static assert(is(typeof(wmean!float([1, 2, 3])) == float));
Examples:
Weighted mean of vector
import mir.ndslice.slice: sliced;
import mir.ndslice.topology: iota, map;
import mir.test: shouldApprox;

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;
auto w = iota([12], 1);
auto w_SumToOne = w.map!(a => a / 78.0);

x.wmean.shouldApprox == 29.25 / 12;
x.wmean(w).shouldApprox == 203.0 / 78;
x.wmean!true(w_SumToOne).shouldApprox == 203.0 / 78;
Examples:
Weighted mean of matrix
import mir.ndslice.fuse: fuse;
import mir.ndslice.topology: iota, map;
import mir.test: shouldApprox;

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 w = iota([2, 6], 1);
auto w_SumToOne = w.map!(a => a / 78.0);

x.wmean.shouldApprox == 29.25 / 12;
x.wmean(w).shouldApprox == 203.0 / 78;
x.wmean!true(w_SumToOne).shouldApprox == 203.0 / 78;
Examples:
Column mean of matrix
import mir.algorithm.iteration: all;
import mir.math.common: approxEqual;
import mir.ndslice.fuse: fuse;
import mir.ndslice.topology: alongDim, byDim, iota, map, universal;

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 w = iota([2], 1).universal;
auto result = [4.0 / 3, 16.0 / 3, 11.5 / 3, 4.0 / 3, 6.5 / 3, 4.25 / 3];

// Use byDim or alongDim with map to compute mean of row/column.
assert(x.byDim!1.map!(a => a.wmean(w)).all!approxEqual(result));
assert(x.alongDim!0.map!(a => a.wmean(w)).all!approxEqual(result));

// FIXME
// Without using map, computes the mean of the whole slice
// assert(x.byDim!1.wmean(w) == x.sliced.wmean);
// assert(x.alongDim!0.wmean(w) == x.sliced.wmean);
Examples:
Can also set algorithm or output type
import mir.ndslice.slice: sliced;
import mir.ndslice.topology: repeat, universal;
import mir.test: shouldApprox;

//Set sum algorithm (also for weights) or output type

auto a = [1, 1e100, 1, -1e100].sliced;

auto x = a * 10_000;
auto w1 = [1, 1, 1, 1].sliced;
auto w2 = [0.25, 0.25, 0.25, 0.25].sliced;

x.wmean!"kbn"(w1).shouldApprox == 20_000 / 4;
x.wmean!(true, "kbn")(w2).shouldApprox == 20_000 / 4;
x.wmean!("kbn", true)(w2).shouldApprox == 20_000 / 4;
x.wmean!("kbn", true, "pairwise")(w2).shouldApprox == 20_000 / 4;
x.wmean!(true, "kbn", "pairwise")(w2).shouldApprox == 20_000 / 4;
x.wmean!"kb2"(w1).shouldApprox == 20_000 / 4;
x.wmean!"precise"(w1).shouldApprox == 20_000 / 4;
x.wmean!(double, "precise")(w1).shouldApprox == 20_000.0 / 4;

auto y = uint.max.repeat(3);
y.wmean!ulong([1, 1, 1].sliced.universal).shouldApprox == 12884901885 / 3;
Examples:
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;
import mir.test: shouldApprox;

auto x = [0, 1, 1, 2, 4, 4,
          2, 7, 5, 1, 2, 0].sliced;
auto w = [1, 2, 3,  4,  5,  6,
          7, 8, 9, 10, 11, 12].sliced;

auto y = x.wmean(w);
y.shouldApprox(1.0e-10) == 204.0 / 78;
static assert(is(typeof(y) == double));

x.wmean!float(w).shouldApprox(1.0e-10) == 204f / 78;
Examples:
Mean works for complex numbers and other user-defined types (provided they can be converted to a floating point or complex type)
import mir.complex;
import mir.ndslice.slice: sliced;
import mir.test: should;
alias C = Complex!double;

auto x = [C(1.0, 2), C(2, 3), C(3, 4), C(4, 5)].sliced;
auto w = [1, 2, 3, 4].sliced;
x.wmean(w).should == C(3, 4);
Examples:
Compute weighted mean tensors along specified dimention of tensors
import mir.ndslice.fuse: fuse;
import mir.ndslice.slice: sliced;
import mir.ndslice.topology: alongDim, as, iota, map, universal;
/++
  [[0,1,2],
   [3,4,5]]
 +/
auto x = [
    [0, 1, 2],
    [3, 4, 5]
].fuse.as!double;
auto w = [
    [1, 2, 3],
    [4, 5, 6]
].fuse;
auto w1 = [1, 2].sliced.universal;
auto w2 = [1, 2, 3].sliced;

assert(x.wmean(w) == (70.0 / 21));

auto m0 = [(0.0 + 6.0) / 3, (1.0 + 8.0) / 3, (2.0 + 10.0) / 3];
assert(x.alongDim!0.map!(a => a.wmean(w1)) == m0);
assert(x.alongDim!(-2).map!(a => a.wmean(w1)) == m0);

auto m1 = [(0.0 + 2.0 + 6.0) / 6, (3.0 + 8.0 + 15.0) / 6];
assert(x.alongDim!1.map!(a => a.wmean(w2)) == m1);
assert(x.alongDim!(-1).map!(a => a.wmean(w2)) == m1);

assert(iota(2, 3, 4, 5).as!double.alongDim!0.map!wmean == iota([3, 4, 5], 3 * 4 * 5 / 2));
meanType!F wmean(SliceA, SliceB)(SliceA s, SliceB w)
if (isConvertibleToSlice!SliceA && isConvertibleToSlice!SliceB);
Parameters:
SliceA s slice-like
SliceB w weights
meanType!F wmean(Range)(Range r)
if (isIterable!Range);
Parameters:
Range r range, must be finite iterable
struct WSummator(T, Summation summation, U = T);
Output range for wsum.
Examples:
import mir.math.sum: Summation;
import mir.ndslice.slice: sliced;
import mir.test: should;

WSummator!(double, Summation.pairwise) x;
x.put([0.0, 1, 2, 3, 4].sliced, [1, 2, 3, 4, 5].sliced);
x.wsum.should == 40;
x.put(5, 6);
x.wsum.should == 70;
Examples:
Assume no weights, like Summator
import mir.math.sum: Summation;
import mir.ndslice.slice: sliced;
import mir.test: should;

WSummator!(double, Summation.pairwise) x;
x.put([0.0, 1, 2, 3, 4].sliced);
x.wsum.should == 10;
x.put(5);
x.wsum.should == 15;
Summator!(T, summation) wsummator;
const pure nothrow @nogc @property @safe F wsum(F = T)();
void put(Slice1, Slice2)(Slice1 s, Slice2 w)
if (isSlice!Slice1 && isSlice!Slice2);
void put(SliceLike1, SliceLike2)(SliceLike1 s, SliceLike2 w)
if (isConvertibleToSlice!SliceLike1 && !isSlice!SliceLike1 && isConvertibleToSlice!SliceLike2 && !isSlice!SliceLike2);
void put(Range)(Range r)
if (isIterable!Range);
void put(RangeA, RangeB)(RangeA r, RangeB w)
if (isInputRange!RangeA && !isConvertibleToSlice!RangeA && isInputRange!RangeB && !isConvertibleToSlice!RangeB);
void put()(T x, U w);
void put()(T x);
void put(F = T, G = U)(WSummator!(F, summation, G) wm);
template wsum(F, Summation summation = Summation.appropriate, G = F)

template wsum(Summation summation = Summation.appropriate)

template wsum(F, string summation, G = F)

template wsum(string summation)
Computes the weighted sum of the input.
Parameters:
F controls type of output
summation algorithm for calculating sums (default: Summation.appropriate)
G controls the type of weights
Returns:
The weighted sum of all the elements in the input
See Also:
Examples:
import mir.complex;
import mir.math.common: approxEqual;
import mir.ndslice.slice: sliced;
import mir.test: should;
alias C = Complex!double;

wsum([1, 2, 3], [1, 2, 3]).should == (1 + 4 + 9);
wsum([C(1, 3), C(2), C(3)], [1, 2, 3]).should == C((1 + 4 + 9), 3);

wsum!float([0, 1, 2, 3, 4, 5].sliced(3, 2), [1, 2, 3, 4, 5, 6].sliced(3, 2)).should == 70;

static assert(is(typeof(wmean!float([1, 2, 3], [1, 2, 3])) == float));
Examples:
If weights are not provided, then behaves like sum
import mir.complex;
import mir.ndslice.slice: sliced;
import mir.test: should;
alias C = Complex!double;

wsum([1.0, 2, 3]).should == 6;
wsum([C(1, 3), C(2), C(3)]).should == C(6, 3);

wsum!float([0, 1, 2, 3, 4, 5].sliced(3, 2)).should == 15;

static assert(is(typeof(wsum!float([1, 2, 3])) == float));
Examples:
Weighted sum of vector
import mir.ndslice.slice: sliced;
import mir.ndslice.topology: iota, map;
import mir.test: should;

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;
auto w = iota([12], 1);

x.wsum.should == 29.25;
x.wsum(w).should == 203;
Examples:
Weighted sum of matrix
import mir.ndslice.fuse: fuse;
import mir.ndslice.topology: iota;
import mir.test: should;

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 w = iota([2, 6], 1);

x.wsum.should == 29.25;
x.wsum(w).should == 203;
Examples:
Column sum of matrix
import mir.algorithm.iteration: all;
import mir.math.common: approxEqual;
import mir.ndslice.fuse: fuse;
import mir.ndslice.topology: alongDim, byDim, iota, map, universal;

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 w = iota([2], 1).universal;
auto result = [4, 16, 11.5, 4, 6.5, 4.25];

// Use byDim or alongDim with map to compute sum of row/column.
assert(x.byDim!1.map!(a => a.wsum(w)).all!approxEqual(result));
assert(x.alongDim!0.map!(a => a.wsum(w)).all!approxEqual(result));

// FIXME
// Without using map, computes the sum of the whole slice
// assert(x.byDim!1.wsum(w) == x.sliced.wsum);
// assert(x.alongDim!0.wsum(w) == x.sliced.wsum);
Examples:
Can also set algorithm or output type
import mir.ndslice.slice: sliced;
import mir.ndslice.topology: repeat, universal;
import mir.test: should;

//Set sum algorithm (also for weights) or output type

auto a = [1, 1e100, 1, -1e100].sliced;

auto x = a * 10_000;
auto w1 = [1, 1, 1, 1].sliced;
auto w2 = [0.25, 0.25, 0.25, 0.25].sliced;

x.wsum!"kbn"(w1).should == 20_000;
x.wsum!"kbn"(w2).should == 20_000 / 4;
x.wsum!"kb2"(w1).should == 20_000;
x.wsum!"precise"(w1).should == 20_000;
x.wsum!(double, "precise")(w1).should == 20_000;

auto y = uint.max.repeat(3);
y.wsum!ulong([1, 1, 1].sliced.universal).should == 12884901885;
Examples:
wsum works for complex numbers and other user-defined types
import mir.complex;
import mir.ndslice.slice: sliced;
import mir.test: should;
alias C = Complex!double;

auto x = [C(1.0, 2), C(2, 3), C(3, 4), C(4, 5)].sliced;
auto w = [1, 2, 3, 4].sliced;
x.wsum(w).should == C(30, 40);
Examples:
Compute weighted sum tensors along specified dimention of tensors
import mir.ndslice.fuse: fuse;
import mir.ndslice.slice: sliced;
import mir.ndslice.topology: alongDim, as, iota, map, universal;
/++
  [[0,1,2],
   [3,4,5]]
 +/
auto x = [
    [0, 1, 2],
    [3, 4, 5]
].fuse.as!double;
auto w = [
    [1, 2, 3],
    [4, 5, 6]
].fuse;
auto w1 = [1, 2].sliced.universal;
auto w2 = [1, 2, 3].sliced;

assert(x.wsum(w) == 70);

auto m0 = [(0 + 6), (1 + 8), (2 + 10)];
assert(x.alongDim!0.map!(a => a.wsum(w1)) == m0);
assert(x.alongDim!(-2).map!(a => a.wsum(w1)) == m0);

auto m1 = [(0 + 2 + 6), (3 + 8 + 15)];
assert(x.alongDim!1.map!(a => a.wsum(w2)) == m1);
assert(x.alongDim!(-1).map!(a => a.wsum(w2)) == m1);
sumType!F wsum(SliceA, SliceB)(SliceA s, SliceB w)
if (isConvertibleToSlice!SliceA && isConvertibleToSlice!SliceB);
Parameters:
SliceA s slice-like
SliceB w weights
sumType!F wsum(Range)(Range r)
if (isIterable!Range);
Parameters:
Range r range, must be finite iterable