Merge branch 'master' of github.com:foretold-app/estiband

* 'master' of github.com:foretold-app/estiband: (32 commits)
  Fixes CdfLibrary.js
  Fixes CdfLibrary.js
  Adds integral function
  Adds integral function
  Adds tests
  Adds tests
  FindX tests
  FindY tests (2)
  FindY tests
  Renames CDFunctor
  Findx
  FindY
  FindY
  Adds tests (5)
  Adds tests (4)
  Adds tests (3)
  Adds tests (2)
  Adds tests
  Adds a "range" function
  Fixes file link
  ...
This commit is contained in:
Ozzie Gooen 2020-02-26 09:16:15 +00:00
commit eece6cf95e
6 changed files with 579 additions and 180 deletions

179
__tests__/CDF__Test.re Normal file
View File

@ -0,0 +1,179 @@
open Jest;
open Expect;
exception ShapeWrong(string);
describe("CDF", () => {
test("raise - w/o order", () => {
expect(() => {
module Cdf =
CDF.Make({
let shape: DistTypes.xyShape = {
xs: [|10., 4., 8.|],
ys: [|8., 9., 2.|],
};
});
();
})
|> toThrow
});
test("raise - with order", () => {
expect(() => {
module Cdf =
CDF.Make({
let shape: DistTypes.xyShape = {
xs: [|1., 4., 8.|],
ys: [|8., 9., 2.|],
};
});
();
})
|> not_
|> toThrow
});
test("order#1", () => {
let a = CDF.order({xs: [|1., 4., 8.|], ys: [|8., 9., 2.|]});
let b: DistTypes.xyShape = {xs: [|1., 4., 8.|], ys: [|8., 9., 2.|]};
expect(a) |> toEqual(b);
});
test("order#2", () => {
let a = CDF.order({xs: [|10., 5., 12.|], ys: [|8., 9., 2.|]});
let b: DistTypes.xyShape = {xs: [|5., 10., 12.|], ys: [|9., 8., 2.|]};
expect(a) |> toEqual(b);
});
describe("minX - maxX", () => {
module Dist =
CDF.Make({
let shape = CDF.order({xs: [|20., 4., 8.|], ys: [|8., 9., 2.|]});
});
test("minX", () => {
expect(Dist.minX()) |> toEqual(4.)
});
test("maxX", () => {
expect(Dist.maxX()) |> toEqual(20.)
});
});
describe("findY", () => {
module Dist =
CDF.Make({
let shape = CDF.order({xs: [|1., 2., 3.|], ys: [|5., 6., 7.|]});
});
test("#1", () => {
expect(Dist.findY(1.)) |> toEqual(5.)
});
test("#2", () => {
expect(Dist.findY(1.5)) |> toEqual(5.5)
});
test("#3", () => {
expect(Dist.findY(3.)) |> toEqual(7.)
});
test("#4", () => {
expect(Dist.findY(4.)) |> toEqual(7.)
});
test("#5", () => {
expect(Dist.findY(15.)) |> toEqual(7.)
});
test("#6", () => {
expect(Dist.findY(-1.)) |> toEqual(5.)
});
});
describe("findX", () => {
module Dist =
CDF.Make({
let shape = CDF.order({xs: [|1., 2., 3.|], ys: [|5., 6., 7.|]});
});
test("#1", () => {
expect(Dist.findX(5.)) |> toEqual(1.)
});
test("#2", () => {
expect(Dist.findX(7.)) |> toEqual(3.)
});
test("#3", () => {
expect(Dist.findX(5.5)) |> toEqual(1.5)
});
test("#4", () => {
expect(Dist.findX(8.)) |> toEqual(3.)
});
test("#5", () => {
expect(Dist.findX(4.)) |> toEqual(1.)
});
});
describe("convertWithAlternativeXs", () => {
open Functions;
let xs = up(1, 9);
let ys = up(20, 28);
module Dist =
CDF.Make({
let shape = CDF.order({xs, ys});
});
let xs2 = up(3, 7);
module Dist2 =
CDF.Make({
let shape = Dist.convertWithAlternativeXs(xs2);
});
test("#1", () => {
expect(Dist2.xs) |> toEqual([|3., 4., 5., 6., 7.|])
});
test("#2", () => {
expect(Dist2.ys) |> toEqual([|22., 23., 24., 25., 26.|])
});
});
describe("convertToNewLength", () => {
open Functions;
let xs = up(1, 9);
let ys = up(50, 58);
module Dist =
CDF.Make({
let shape = CDF.order({xs, ys});
});
module Dist2 =
CDF.Make({
let shape = Dist.convertToNewLength(3);
});
test("#1", () => {
expect(Dist2.xs) |> toEqual([|1., 5., 9.|])
});
test("#2", () => {
expect(Dist2.ys) |> toEqual([|50., 54., 58.|])
});
});
// @todo: Should each test expect 70.?
describe("sample", () => {
open Functions;
let xs = up(1, 9);
let ys = up(70, 78);
module Dist =
CDF.Make({
let shape = CDF.order({xs, ys});
});
let xs2 = Dist.sample(3);
test("#1", () => {
expect(xs2[0]) |> toBe(70.)
});
test("#2", () => {
expect(xs2[1]) |> toBe(70.)
});
test("#3", () => {
expect(xs2[2]) |> toBe(70.)
});
});
describe("integral", () => {
module Dist =
CDF.Make({
let shape =
CDF.order({xs: [|0., 1., 2., 4.|], ys: [|0.0, 1.0, 2.0, 2.0|]});
});
test("with regular inputs", () => {
expect(Dist.integral()) |> toBe(6.)
});
});
});

View File

@ -0,0 +1,92 @@
open Jest;
open Expect;
exception ShapeWrong(string);
describe("Functions", () => {
test("interpolate", () => {
let a = Functions.interpolate(10., 20., 1., 2., 15.);
let b = 1.5;
expect(a) |> toEqual(b);
});
test("range#1", () => {
expect(Functions.range(1., 5., 3)) |> toEqual([|1., 3., 5.|])
});
test("range#2", () => {
expect(Functions.range(1., 5., 5)) |> toEqual([|1., 2., 3., 4., 5.|])
});
test("range#3", () => {
expect(Functions.range(-10., 15., 2)) |> toEqual([|(-10.), 15.|])
});
test("range#4", () => {
expect(Functions.range(-10., 15., 3)) |> toEqual([|(-10.), 2.5, 15.|])
});
test("range#5", () => {
expect(Functions.range(-10.3, 17., 3))
|> toEqual([|(-10.3), 3.3499999999999996, 17.|])
});
test("range#6", () => {
expect(Functions.range(-10.3, 17., 5))
|> toEqual([|
(-10.3),
(-3.4750000000000005),
3.3499999999999996,
10.175,
17.0,
|])
});
test("range#7", () => {
expect(Functions.range(-10.3, 17.31, 3))
|> toEqual([|(-10.3), 3.504999999999999, 17.31|])
});
test("range#8", () => {
expect(Functions.range(1., 1., 3)) |> toEqual([|1., 1., 1.|])
});
test("mean#1", () => {
expect(Functions.mean([|1., 2., 3.|])) |> toEqual(2.)
});
test("mean#2", () => {
expect(Functions.mean([|1., 2., 3., (-2.)|])) |> toEqual(1.)
});
test("mean#3", () => {
expect(Functions.mean([|1., 2., 3., (-2.), (-10.)|])) |> toEqual(-1.2)
});
test("min#1", () => {
expect(Functions.min([|1., 2., 3.|])) |> toEqual(1.)
});
test("min#2", () => {
expect(Functions.min([|(-1.), (-2.), 0., 20.|])) |> toEqual(-2.)
});
test("min#3", () => {
expect(Functions.min([|(-1.), (-2.), 0., 20., (-2.2)|]))
|> toEqual(-2.2)
});
test("max#1", () => {
expect(Functions.max([|1., 2., 3.|])) |> toEqual(3.)
});
test("max#2", () => {
expect(Functions.max([|(-1.), (-2.), 0., 20.|])) |> toEqual(20.)
});
test("max#3", () => {
expect(Functions.max([|(-1.), (-2.), 0., (-2.2)|])) |> toEqual(0.)
});
test("random#1", () => {
expect(Functions.random(1, 5)) |> toBeLessThanOrEqual(5)
});
test("random#2", () => {
expect(Functions.random(1, 5)) |> toBeGreaterThanOrEqual(1)
});
test("up#1", () => {
expect(Functions.up(1, 5)) |> toEqual([|1., 2., 3., 4., 5.|])
});
test("up#2", () => {
expect(Functions.up(-1, 5))
|> toEqual([|(-1.), 0., 1., 2., 3., 4., 5.|])
});
test("down#1", () => {
expect(Functions.down(5, 1)) |> toEqual([|5., 4., 3., 2., 1.|])
});
test("down#2", () => {
expect(Functions.down(5, -1))
|> toEqual([|5., 4., 3., 2., 1., 0., (-1.)|])
});
});

View File

@ -1,11 +0,0 @@
'use strict';
var React = require("react");
var ReactDOMRe = require("reason-react/src/ReactDOMRe.js");
var App$ProbExample = require("./App.bs.js");
((import('./styles/index.css')));
ReactDOMRe.renderToElementWithId(React.createElement(App$ProbExample.make, { }), "app");
/* Not a pure module */

View File

@ -1,174 +1,170 @@
const { const {
Cdf, Cdf,
Pdf, Pdf,
ContinuousDistribution, ContinuousDistribution,
ContinuousDistributionCombination, ContinuousDistributionCombination,
scoringFunctions, scoringFunctions,
} = require("@foretold/cdf/lib"); } = require("@foretold/cdf/lib");
const _ = require("lodash"); const _ = require("lodash");
/**
*
* @param xs
* @param ys
* @returns {{ys: *, xs: *}}
*/
function cdfToPdf({ xs, ys }) {
let cdf = new Cdf(xs, ys);
let pdf = cdf.toPdf();
return { xs: pdf.xs, ys: pdf.ys };
}
/** /**
* *
* @param xs * @param xs
* @param ys * @param ys
* @returns {{ys: *, xs: *}} * @returns {{ys: *, xs: *}}
*/ */
function pdfToCdf({ xs, ys }) { function cdfToPdf({ xs, ys }) {
let cdf = new Pdf(xs, ys); let cdf = new Cdf(xs, ys);
let pdf = cdf.toCdf(); let pdf = cdf.toPdf();
return { xs: pdf.xs, ys: pdf.ys }; return { xs: pdf.xs, ys: pdf.ys };
} }
/**
*
* @param sampleCount
* @param vars
* @returns {{ys: *, xs: *}}
*/
function mean(sampleCount, vars) {
let cdfs = vars.map(r => new Cdf(r.xs, r.ys));
let comb = new ContinuousDistributionCombination(cdfs);
let newCdf = comb.combineYsWithMean(sampleCount);
return { xs: newCdf.xs, ys: newCdf.ys };
}
/**
*
* @param sampleCount
* @param predictionCdf
* @param resolutionCdf
*/
function scoreNonMarketCdfCdf(sampleCount, predictionCdf, resolutionCdf, resolutionUniformAdditionWeight=0) {
let toCdf = (r) => (new Cdf(r.xs, r.ys));
let prediction = toCdf(predictionCdf);
if (_.isFinite(resolutionUniformAdditionWeight)){
prediction = prediction.combineWithUniformOfCdf(
{
cdf: toCdf(resolutionCdf),
uniformWeight: resolutionUniformAdditionWeight,
sampleCount
}
);
}
return scoringFunctions.distributionInputDistributionOutputMarketless({
predictionCdf: prediction,
resultCdf: toCdf(resolutionCdf),
sampleCount,
});
}
/**
*
* @param sampleCount
* @param cdf
*/
function differentialEntropy(sampleCount, cdf) {
let toCdf = (r) => (new Cdf(r.xs, r.ys));
return scoringFunctions.differentialEntropy({
cdf: toCdf(cdf),
sampleCount: sampleCount
});
}
/**
*
* @param x
* @param xs
* @param ys
* @returns {number}
*/
function findY(x, { xs, ys }) {
let cdf = new Cdf(xs, ys);
return cdf.findY(x);
}
/** /**
* *
* @param x * @param xs
* @param xs * @param ys
* @param ys * @returns {{ys: *, xs: *}}
* @returns {number[]} */
*/ function pdfToCdf({ xs, ys }) {
function convertToNewLength(n, { xs, ys }) { let cdf = new Pdf(xs, ys);
let dist = new ContinuousDistribution(xs, ys); let pdf = cdf.toCdf();
return dist.convertToNewLength(n); return { xs: pdf.xs, ys: pdf.ys };
} }
/** /**
* *
* @param y * @param sampleCount
* @param xs * @param vars
* @param ys * @returns {{ys: *, xs: *}}
* @returns {number} */
*/ function mean(sampleCount, vars) {
function findX(y, { xs, ys }) { let cdfs = vars.map(r => new Cdf(r.xs, r.ys));
let cdf = new Cdf(xs, ys); let comb = new ContinuousDistributionCombination(cdfs);
return cdf.findX(y); let newCdf = comb.combineYsWithMean(sampleCount);
}
return { xs: newCdf.xs, ys: newCdf.ys };
/** }
*
* @param xs /**
* @param ys *
* @returns {number[]} * @param sampleCount
*/ * @param predictionCdf
function integral({ xs, ys }) { * @param resolutionCdf
if (_.includes(ys, NaN)){ */
return NaN; function scoreNonMarketCdfCdf(sampleCount, predictionCdf, resolutionCdf, resolutionUniformAdditionWeight = 0) {
} let toCdf = (r) => (new Cdf(r.xs, r.ys));
else if (_.includes(ys, Infinity) && _.includes(ys, -Infinity)){ let prediction = toCdf(predictionCdf);
return NaN; if (_.isFinite(resolutionUniformAdditionWeight)) {
} prediction = prediction.combineWithUniformOfCdf(
else if (_.includes(ys, Infinity)){ {
return Infinity; cdf: toCdf(resolutionCdf),
} uniformWeight: resolutionUniformAdditionWeight,
else if (_.includes(ys, -Infinity)){ sampleCount
return -Infinity;
}
let integral = 0;
for (let i = 1; i < ys.length; i++) {
let thisY = ys[i];
let lastY = ys[i - 1];
let thisX = xs[i];
let lastX = xs[i - 1];
if (
_.isFinite(thisY) && _.isFinite(lastY) &&
_.isFinite(thisX) && _.isFinite(lastX)
) {
let sectionInterval = ((thisY + lastY) / 2) * (thisX - lastX);
integral = integral + sectionInterval;
} }
);
}
return integral;
} }
module.exports = { return scoringFunctions.distributionInputDistributionOutputMarketless({
cdfToPdf, predictionCdf: prediction,
pdfToCdf, resultCdf: toCdf(resolutionCdf),
findY, sampleCount,
findX, });
convertToNewLength, }
mean,
scoreNonMarketCdfCdf, /**
differentialEntropy, *
integral, * @param sampleCount
}; * @param cdf
*/
function differentialEntropy(sampleCount, cdf) {
let toCdf = (r) => (new Cdf(r.xs, r.ys));
return scoringFunctions.differentialEntropy({
cdf: toCdf(cdf),
sampleCount: sampleCount
});
}
/**
*
* @param x
* @param xs
* @param ys
* @returns {number}
*/
function findY(x, { xs, ys }) {
let cdf = new Cdf(xs, ys);
return cdf.findY(x);
}
/**
*
* @param x
* @param xs
* @param ys
* @returns {number[]}
*/
function convertToNewLength(n, { xs, ys }) {
let dist = new ContinuousDistribution(xs, ys);
return dist.convertToNewLength(n);
}
/**
*
* @param y
* @param xs
* @param ys
* @returns {number}
*/
function findX(y, { xs, ys }) {
let cdf = new Cdf(xs, ys);
return cdf.findX(y);
}
/**
*
* @param xs
* @param ys
* @returns {number[]}
*/
function integral({ xs, ys }) {
if (_.includes(ys, NaN)) {
return NaN;
} else if (_.includes(ys, Infinity) && _.includes(ys, -Infinity)) {
return NaN;
} else if (_.includes(ys, Infinity)) {
return Infinity;
} else if (_.includes(ys, -Infinity)) {
return -Infinity;
}
let integral = 0;
for (let i = 1; i < ys.length; i++) {
let thisY = ys[i];
let lastY = ys[i - 1];
let thisX = xs[i];
let lastX = xs[i - 1];
if (
_.isFinite(thisY) && _.isFinite(lastY) &&
_.isFinite(thisX) && _.isFinite(lastX)
) {
let sectionInterval = ((thisY + lastY) / 2) * (thisX - lastX);
integral = integral + sectionInterval;
}
}
return integral;
}
module.exports = {
cdfToPdf,
pdfToCdf,
findY,
findX,
convertToNewLength,
mean,
scoreNonMarketCdfCdf,
differentialEntropy,
integral,
};

107
src/utility/lib/CDF.re Normal file
View File

@ -0,0 +1,107 @@
module type Config = {let shape: DistTypes.xyShape;};
exception ShapeWrong(string);
let order = (shape: DistTypes.xyShape): DistTypes.xyShape => {
let xy =
shape.xs
|> Array.mapi((i, x) => [x, shape.ys |> Array.get(_, i)])
|> Belt.SortArray.stableSortBy(_, ([a, _], [b, _]) => a > b ? 1 : (-1));
{
xs: xy |> Array.map(([x, _]) => x),
ys: xy |> Array.map(([_, y]) => y),
};
};
module Make = (Config: Config) => {
let xs = Config.shape.xs;
let ys = Config.shape.ys;
let get = Array.get;
let len = Array.length;
let validateHasLength = (): bool => len(xs) > 0;
let validateSize = (): bool => len(xs) == len(ys);
if (!validateHasLength()) {
raise(ShapeWrong("You need at least one element."));
};
if (!validateSize()) {
raise(ShapeWrong("Arrays of \"xs\" and \"ys\" have different sizes."));
};
if (!Belt.SortArray.isSorted(xs, (a, b) => a > b ? 1 : (-1))) {
raise(ShapeWrong("Arrays of \"xs\" and \"ys\" have different sizes."));
};
let minX = () => get(xs, 0);
let maxX = () => get(xs, len(xs) - 1);
let minY = () => get(ys, 0);
let maxY = () => get(ys, len(ys) - 1);
let findY = (x: float): float => {
let firstHigherIndex = Belt.Array.getIndexBy(xs, e => e >= x);
switch (firstHigherIndex) {
| None => maxY()
| Some(0) => minY()
| Some(firstHigherIndex) =>
let lowerOrEqualIndex =
firstHigherIndex - 1 < 0 ? 0 : firstHigherIndex - 1;
let needsInterpolation = get(xs, lowerOrEqualIndex) != x;
if (needsInterpolation) {
Functions.interpolate(
get(xs, lowerOrEqualIndex),
get(xs, firstHigherIndex),
get(ys, lowerOrEqualIndex),
get(ys, firstHigherIndex),
x,
);
} else {
ys[lowerOrEqualIndex];
};
};
};
let findX = (y: float): float => {
let firstHigherIndex = Belt.Array.getIndexBy(ys, e => e >= y);
switch (firstHigherIndex) {
| None => maxX()
| Some(0) => minX()
| Some(firstHigherIndex) =>
let lowerOrEqualIndex =
firstHigherIndex - 1 < 0 ? 0 : firstHigherIndex - 1;
let needsInterpolation = get(ys, lowerOrEqualIndex) != y;
if (needsInterpolation) {
Functions.interpolate(
get(ys, lowerOrEqualIndex),
get(ys, firstHigherIndex),
get(xs, lowerOrEqualIndex),
get(xs, firstHigherIndex),
y,
);
} else {
xs[lowerOrEqualIndex];
};
};
};
let convertWithAlternativeXs = (newXs: array(float)): DistTypes.xyShape => {
let newYs = Belt.Array.map(newXs, findY);
{xs: newXs, ys: newYs};
};
let convertToNewLength = (newLength: int): DistTypes.xyShape => {
Functions.(
range(min(xs), max(xs), newLength) |> convertWithAlternativeXs
);
};
let sampleSingle = (): float => Js.Math.random() |> findY;
let sample = (size: int): array(float) =>
Belt.Array.makeBy(size, i => sampleSingle());
let integral = () => {
Belt.Array.reduceWithIndex(ys, 0., (integral, y, i) => {
switch (i) {
| 0 => integral
| _ =>
let thisY = y;
let lastY = get(ys, i - 1);
let thisX = get(xs, i);
let lastX = get(xs, i - 1);
let sectionInterval = (thisY +. lastY) /. 2. *. (thisX -. lastX);
integral +. sectionInterval;
}
});
};
};

View File

@ -0,0 +1,36 @@
exception RangeWrong(string);
let interpolate =
(xMin: float, xMax: float, yMin: float, yMax: float, xIntended: float)
: float => {
let minProportion = (xMax -. xIntended) /. (xMax -. xMin);
let maxProportion = (xIntended -. xMin) /. (xMax -. xMin);
yMin *. minProportion +. yMax *. maxProportion;
};
let sum = Belt.Array.reduce(_, 0., (i, j) => i +. j);
let mean = a => sum(a) /. (Array.length(a) |> float_of_int);
let min = a => Belt.Array.reduce(a, a[0], (i, j) => i < j ? i : j);
let max = a => Belt.Array.reduce(a, a[0], (i, j) => i > j ? i : j);
let up = (a, b) =>
Array.make(b - a + 1, a)
|> Array.mapi((i, c) => c + i)
|> Belt.Array.map(_, float_of_int);
let down = (a, b) =>
Array.make(a - b + 1, a)
|> Array.mapi((i, c) => c - i)
|> Belt.Array.map(_, float_of_int);
let range = (min: float, max: float, n: int): array(float) => {
switch (n) {
| 0 => [||]
| 1 => [|min|]
| 2 => [|min, max|]
| _ when min == max => Belt.Array.make(n, min)
| _ when n < 0 => raise(RangeWrong("n is less then zero"))
| _ when min > max => raise(RangeWrong("Min values is less then max"))
| _ =>
let diff = (max -. min) /. Belt.Float.fromInt(n - 1);
Belt.Array.makeBy(n, i => {min +. Belt.Float.fromInt(i) *. diff});
};
};
let random = Js.Math.random_int;