diff --git a/__tests__/Distributions__Test.re b/__tests__/Distributions__Test.re index c2cfd026..aed93c8c 100644 --- a/__tests__/Distributions__Test.re +++ b/__tests__/Distributions__Test.re @@ -13,16 +13,15 @@ let makeTest = (~only=false, str, item1, item2) => ); let makeTestCloseEquality = (~only=false, str, item1, item2, ~digits) => -only - ? Only.test(str, () => - expect(item1) |> toBeSoCloseTo(item2, ~digits) - ) - : test(str, () => - expect(item1) |> toBeSoCloseTo(item2, ~digits) - ); + only + ? Only.test(str, () => + expect(item1) |> toBeSoCloseTo(item2, ~digits) + ) + : test(str, () => + expect(item1) |> toBeSoCloseTo(item2, ~digits) + ); describe("Shape", () => { - describe("Continuous", () => { open Distributions.Continuous; let continuous = make(`Linear, shape); @@ -129,7 +128,7 @@ describe("Shape", () => { 1.0, ); }); - + describe("Discrete", () => { open Distributions.Discrete; let shape: DistTypes.xyShape = { @@ -195,7 +194,13 @@ describe("Shape", () => { 0.9, ); makeTest("integralEndY", T.Integral.sum(~cache=None, discrete), 1.0); - + makeTest("mean", T.getMean(discrete), 3.9); + makeTestCloseEquality( + "variance", + T.getVariance(discrete), + 5.89, + ~digits=7, + ); }); describe("Mixed", () => { @@ -300,7 +305,6 @@ describe("Shape", () => { }, ), ); - }); describe("Distplus", () => { @@ -380,33 +384,36 @@ describe("Shape", () => { let stdev = 4.0; let variance = stdev ** 2.0; let numSamples = 10000; - open Distributions.Shape; - let normal: SymbolicDist.dist = `Normal({ mean, stdev}); + let normal: SymbolicDist.dist = `Normal({mean, stdev}); let normalShape = SymbolicDist.GenericSimple.toShape(normal, numSamples); let lognormal = SymbolicDist.Lognormal.fromMeanAndStdev(mean, stdev); - let lognormalShape = SymbolicDist.GenericSimple.toShape(lognormal, numSamples); + let lognormalShape = + SymbolicDist.GenericSimple.toShape(lognormal, numSamples); makeTestCloseEquality( "Mean of a normal", - T.getMean(normalShape), - mean, - ~digits=2); + T.getMean(normalShape), + mean, + ~digits=2, + ); makeTestCloseEquality( - "Variance of a normal", - T.getVariance(normalShape), - variance, - ~digits=1); + "Variance of a normal", + T.getVariance(normalShape), + variance, + ~digits=1, + ); makeTestCloseEquality( - "Mean of a lognormal", - T.getMean(lognormalShape), - mean, - ~digits=2); + "Mean of a lognormal", + T.getMean(lognormalShape), + mean, + ~digits=2, + ); makeTestCloseEquality( - "Variance of a lognormal", - T.getVariance(lognormalShape), - variance, - ~digits=0); + "Variance of a lognormal", + T.getVariance(lognormalShape), + variance, + ~digits=0, + ); }); - -}); +}); \ No newline at end of file diff --git a/src/distPlus/distribution/Distributions.re b/src/distPlus/distribution/Distributions.re index bf07336f..a97132d7 100644 --- a/src/distPlus/distribution/Distributions.re +++ b/src/distPlus/distribution/Distributions.re @@ -104,7 +104,7 @@ module Continuous = { ) |> DistTypes.MixedPoint.makeContinuous; }; - + // let combineWithFn = (t1: t, t2: t, fn: (float, float) => float) => { // switch(t1, t2){ // | ({interpolation: `Stepwise}, {interpolation: `Stepwise}) => 3.0 @@ -141,8 +141,22 @@ module Continuous = { let toScaledContinuous = t => Some(t); let toScaledDiscrete = _ => None; - let getMean = (t: t) => XYShape.Analysis.integrateContinuousShape(t); - let getVariance = (t: t): float => XYShape.Analysis.getVarianceDangerously(t, getMean, XYShape.Analysis.getMeanOfSquaresContinuousShape); + let getMean = (t: t) => { + let indefiniteIntegralStepwise = (p, h1) => h1 *. p ** 2.0 /. 2.0; + let indefiniteIntegralLinear = (p, a, b) => + a *. p ** 2.0 /. 2.0 +. b *. p ** 3.0 /. 3.0; + XYShape.Analysis.integrateContinuousShape( + ~indefiniteIntegralStepwise, + ~indefiniteIntegralLinear, + t, + ); + }; + let getVariance = (t: t): float => + XYShape.Analysis.getVarianceDangerously( + t, + getMean, + XYShape.Analysis.getMeanOfSquaresContinuousShape, + ); }); }; @@ -215,13 +229,14 @@ module Discrete = { |> Continuous.getShape |> XYShape.YtoX.linear(f); - let getMean = (t: t): float => E.A.reducei(t.xs, 0.0, (acc, x, i) => acc +. x*. t.ys[i]); + let getMean = (t: t): float => + E.A.reducei(t.xs, 0.0, (acc, x, i) => acc +. x *. t.ys[i]); let getVariance = (t: t): float => { - let getMeanOfSquares = t => getMean(XYShape.Analysis.squareXYShape(t)); + let getMeanOfSquares = t => + getMean(XYShape.Analysis.squareXYShape(t)); XYShape.Analysis.getVarianceDangerously(t, getMean, getMeanOfSquares); }; }); - }; // TODO: I think this shouldn't assume continuous/discrete are normalized to 1.0, and thus should not need the discreteProbabilityMassFraction being separate. @@ -393,27 +408,38 @@ module Mixed = { }; }; - let getMean = (t: t) : float => { - let discreteProbabilityMassFraction = t.discreteProbabilityMassFraction; - let mean = switch(discreteProbabilityMassFraction){ - | 1.0 => Discrete.T.getMean(t.discrete); - | 0.0 => Continuous.T.getMean(t.continuous); - | _ => (Discrete.T.getMean(t.discrete) *. discreteProbabilityMassFraction) - +. (Continuous.T.getMean(t.continuous) *. (1.0 -. discreteProbabilityMassFraction)) + let getMean = (t: t): float => { + let discreteProbabilityMassFraction = + t.discreteProbabilityMassFraction; + switch (discreteProbabilityMassFraction) { + | 1.0 => Discrete.T.getMean(t.discrete) + | 0.0 => Continuous.T.getMean(t.continuous) + | _ => + Discrete.T.getMean(t.discrete) + *. discreteProbabilityMassFraction + +. Continuous.T.getMean(t.continuous) + *. (1.0 -. discreteProbabilityMassFraction) }; - mean; }; - let getVariance = (t: t) : float => { - let discreteProbabilityMassFraction = t.discreteProbabilityMassFraction; + let getVariance = (t: t): float => { + let discreteProbabilityMassFraction = + t.discreteProbabilityMassFraction; let getMeanOfSquares = (t: t) => { - Discrete.T.getMean(XYShape.Analysis.squareXYShape(t.discrete))*.t.discreteProbabilityMassFraction - +. XYShape.Analysis.getMeanOfSquaresContinuousShape(t.continuous)*.(1.0 -. t.discreteProbabilityMassFraction) + Discrete.T.getMean(XYShape.Analysis.squareXYShape(t.discrete)) + *. t.discreteProbabilityMassFraction + +. XYShape.Analysis.getMeanOfSquaresContinuousShape(t.continuous) + *. (1.0 -. t.discreteProbabilityMassFraction); }; - switch(discreteProbabilityMassFraction){ - | 1.0 => Discrete.T.getVariance(t.discrete); - | 0.0 => Continuous.T.getVariance(t.continuous); - | _ => XYShape.Analysis.getVarianceDangerously(t, getMean, getMeanOfSquares); + switch (discreteProbabilityMassFraction) { + | 1.0 => Discrete.T.getVariance(t.discrete) + | 0.0 => Continuous.T.getVariance(t.continuous) + | _ => + XYShape.Analysis.getVarianceDangerously( + t, + getMean, + getMeanOfSquares, + ) }; }; }); @@ -520,18 +546,20 @@ module Shape = { Discrete.T.mapY(fn), Continuous.T.mapY(fn), )); - - let getMean = (t: t): float => switch (t) { - | Mixed(m) => Mixed.T.getMean(m); - | Discrete(m) => Discrete.T.getMean(m); - | Continuous(m) => Continuous.T.getMean(m); - }; - let getVariance = (t: t): float => switch (t) { - | Mixed(m) => Mixed.T.getVariance(m); - | Discrete(m) => Discrete.T.getVariance(m); - | Continuous(m) => Continuous.T.getVariance(m); - }; + let getMean = (t: t): float => + switch (t) { + | Mixed(m) => Mixed.T.getMean(m) + | Discrete(m) => Discrete.T.getMean(m) + | Continuous(m) => Continuous.T.getMean(m) + }; + + let getVariance = (t: t): float => + switch (t) { + | Mixed(m) => Mixed.T.getVariance(m) + | Discrete(m) => Discrete.T.getVariance(m) + | Continuous(m) => Continuous.T.getVariance(m) + }; }); }; diff --git a/src/distPlus/distribution/XYShape.re b/src/distPlus/distribution/XYShape.re index 03bddc92..9ec5a2b2 100644 --- a/src/distPlus/distribution/XYShape.re +++ b/src/distPlus/distribution/XYShape.re @@ -17,7 +17,7 @@ module T = { type ts = array(xyShape); let xs = (t: t) => t.xs; let ys = (t: t) => t.ys; - let empty = ({xs: [||], ys: [||]}); + let empty = {xs: [||], ys: [||]}; let minX = (t: t) => t |> xs |> E.A.Sorted.min |> extImp; let maxX = (t: t) => t |> xs |> E.A.Sorted.max |> extImp; let firstY = (t: t) => t |> ys |> E.A.first |> extImp; @@ -299,55 +299,62 @@ let logScorePoint = (sampleCount, t1, t2) => |> E.O.fmap(Pairs.last) |> E.O.fmap(Pairs.y); - -module Analysis = { - let integrateContinuousShape = ( - ~indefiniteIntegralStepwise = (p,h1) => (h1*.(p**2.0)/. 2.0), - ~indefiniteIntegralLinear = (p, a, b) => (a *. (p ** 2.0) /.2.0) +. (b *. (p**3.0) /. 3.0), - t: DistTypes.continuousShape - ): float => { +module Analysis = { + let integrateContinuousShape = + ( + ~indefiniteIntegralStepwise=(p, h1) => h1 *. p, + ~indefiniteIntegralLinear=(p, a, b) => a *. p +. b *. p ** 2.0 /. 2.0, + t: DistTypes.continuousShape, + ) + : float => { let xs = t.xyShape.xs; let ys = t.xyShape.ys; - E.A.reducei(xs, 0.0, (acc, _x, i) => { - let areaUnderIntegral = switch(t.interpolation, i){ - | (_, 0) => 0.0; - | (`Stepwise, _) => indefiniteIntegralStepwise(xs[i],ys[i-1]) - -. indefiniteIntegralStepwise(xs[i-1],ys[i-1]); - | (`Linear, _) => { - let x1 = xs[i-1]; - let x2 = xs[i]; - let h1 = ys[i-1]; - let h2 = ys[i]; - let b = (h1 -. h2 ) /. (x1 -.x2) - let a = h1 -. b *.x1; - indefiniteIntegralLinear(x2, a, b) -. indefiniteIntegralLinear(x1, a, b); + E.A.reducei( + xs, + 0.0, + (acc, _x, i) => { + let areaUnderIntegral = + switch (t.interpolation, i) { + | (_, 0) => 0.0 + | (`Stepwise, _) => + indefiniteIntegralStepwise(xs[i], ys[i - 1]) + -. indefiniteIntegralStepwise(xs[i - 1], ys[i - 1]) + | (`Linear, _) => + let x1 = xs[i - 1]; + let x2 = xs[i]; + let h1 = ys[i - 1]; + let h2 = ys[i]; + let b = (h1 -. h2) /. (x1 -. x2); + let a = h1 -. b *. x1; + indefiniteIntegralLinear(x2, a, b) + -. indefiniteIntegralLinear(x1, a, b); }; - }; - acc +. areaUnderIntegral; - }); + acc +. areaUnderIntegral; + }, + ); }; - let getVarianceDangerously = ( - t: 't, - getMean: ('t => float), - getMeanOfSquares: ('t => float), - ): float => { - - let meanSquared = getMean(t)**2.0; - let meanOfSquares = getMeanOfSquares(t); - - meanOfSquares -. meanSquared; - }; - - let squareXYShape = t: DistTypes.xyShape => {...t, xs: E.A.fmap(x => x**2.0, t.xs)}; let getMeanOfSquaresContinuousShape = (t: DistTypes.continuousShape) => { - let indefiniteIntegralLinear = (p, a, b) => (a *. (p ** 3.0) /.3.0) +. (b *. (p**4.0) /. 4.0); - let indefiniteIntegralStepwise = (p,h1) => h1*.(p**3.0)/. 3.0; + let indefiniteIntegralLinear = (p, a, b) => + a *. p ** 3.0 /. 3.0 +. b *. p ** 4.0 /. 4.0; + let indefiniteIntegralStepwise = (p, h1) => h1 *. p ** 3.0 /. 3.0; integrateContinuousShape( ~indefiniteIntegralStepwise, ~indefiniteIntegralLinear, - t + t, ); - } + }; + + let getVarianceDangerously = + (t: 't, getMean: 't => float, getMeanOfSquares: 't => float): float => { + let meanSquared = getMean(t) ** 2.0; + let meanOfSquares = getMeanOfSquares(t); + meanOfSquares -. meanSquared; + }; + + let squareXYShape = (t): DistTypes.xyShape => { + ...t, + xs: E.A.fmap(x => x ** 2.0, t.xs), + }; }; \ No newline at end of file