First quick stab at mixed cdf integral

This commit is contained in:
Ozzie Gooen 2020-02-18 19:01:11 +00:00
parent db023c0bef
commit 4d1cc7094f
4 changed files with 168 additions and 126 deletions

View File

@ -9,31 +9,11 @@ let shape: DistributionTypes.xyShape = {
open Shape; open Shape;
describe("Shape", () => describe("Shape", () =>
describe("XYShape", () => { describe("XYShape", () =>
test("#ySum", () => test("#ySum", ()
=>
expect(XYShape.ySum(shape)) |> toEqual(19.0) expect(XYShape.ySum(shape)) |> toEqual(19.0)
); )
test("#volume", () => {
let shape: DistributionTypes.xyShape = {
xs: [|1., 5., 10.|],
ys: [|1., 2., 2.|],
};
expect(XYShape.volume(shape)) |> toEqual(Some(7.0));
});
test("#integral", () => {
let expected: DistributionTypes.xyShape = {
xs: [|1., 4., 8.|],
ys: [|8., 17., 19.|],
};
expect(XYShape.volum2(shape)) |> toEqual(Some(expected));
});
test("#derivative", () => {
let expected: DistributionTypes.xyShape = {
xs: [|1., 4., 8.|],
ys: [|8., 1., 1.|],
};
expect(XYShape.derivative(shape)) |> toEqual(expected);
});
// test("#both", () => { // test("#both", () => {
// let expected: DistributionTypes.xyShape = { // let expected: DistributionTypes.xyShape = {
// xs: [|1., 4., 8.|], // xs: [|1., 4., 8.|],
@ -42,5 +22,5 @@ describe("Shape", () =>
// expect(shape |> XYShape.derivative |> XYShape.integral) // expect(shape |> XYShape.derivative |> XYShape.integral)
// |> toEqual(shape); // |> toEqual(shape);
// }); // });
}) )
); );

View File

@ -8,7 +8,9 @@ let data: DistributionTypes.xyShape = {
let mixedDist = let mixedDist =
GenericDistribution.make( GenericDistribution.make(
~generationSource= ~generationSource=
GuesstimatorString("mm(uniform(10,12), normal(5,1), [.5,.5])"), GuesstimatorString(
"mm(floor(uniform(40, 50)), normal(50,10), [.5,.5])",
),
~probabilityType=Pdf, ~probabilityType=Pdf,
~domain=Complete, ~domain=Complete,
~unit=Unspecified, ~unit=Unspecified,
@ -42,14 +44,14 @@ let distributions = () =>
<h2> {"Basic Mixed Distribution" |> ReasonReact.string} </h2> <h2> {"Basic Mixed Distribution" |> ReasonReact.string} </h2>
<GenericDistributionChart dist=mixedDist /> <GenericDistributionChart dist=mixedDist />
</div> </div>
<div>
<h2> {"Time Distribution" |> ReasonReact.string} </h2>
<GenericDistributionChart dist=timeDist />
</div>
<div>
<h2> {"Domain Limited Distribution" |> ReasonReact.string} </h2>
<GenericDistributionChart dist=domainLimitedDist />
</div>
</div>; </div>;
// <div>
// <h2> {"Time Distribution" |> ReasonReact.string} </h2>
// <GenericDistributionChart dist=timeDist />
// </div>
// <div>
// <h2> {"Domain Limited Distribution" |> ReasonReact.string} </h2>
// <GenericDistributionChart dist=domainLimitedDist />
// </div>
let entry = EntryTypes.(entry(~title="Pdf", ~render=distributions)); let entry = EntryTypes.(entry(~title="Pdf", ~render=distributions));

View File

@ -35,7 +35,11 @@ module Continuous = {
|> ReasonReact.string} |> ReasonReact.string}
</th> </th>
<th className="px-4 py-2 border "> <th className="px-4 py-2 border ">
{Shape.Continuous.findY(x, Shape.XYShape.integral(data)) {Shape.Continuous.findY(
x,
Shape.XYShape.Range.integrateWithTriangles(data)
|> E.O.toExt(""),
)
|> E.Float.with2DigitsPrecision |> E.Float.with2DigitsPrecision
|> ReasonReact.string} |> ReasonReact.string}
</th> </th>
@ -62,7 +66,34 @@ let make = (~dist) => {
}) => }) =>
<div> <div>
<Continuous data=n /> <Continuous data=n />
<Continuous data={n |> Shape.XYShape.integral} /> <Continuous
data={
n
|> Shape.XYShape.Range.integrateWithTriangles
|> E.O.toExt("")
|> Shape.XYShape.scaleCdfTo
}
/>
<Continuous
data={
n
|> Shape.XYShape.Range.integrateWithTriangles
|> E.O.toExt("")
|> Shape.XYShape.Range.derivative
|> E.O.toExt("")
}
/>
<Continuous
data={
n
|> Shape.XYShape.Range.integrateWithTriangles
|> E.O.toExt("")
|> Shape.XYShape.Range.derivative
|> E.O.toExt("")
|> Shape.XYShape.Range.integrateWithTriangles
|> E.O.toExt("")
}
/>
{d |> Shape.Discrete.scaleYToTotal(f) |> Shape.Discrete.render} {d |> Shape.Discrete.scaleYToTotal(f) |> Shape.Discrete.render}
</div> </div>
| _ => <div /> | _ => <div />

View File

@ -19,6 +19,14 @@ module XYShape = {
let fmap = (t: t, y): t => {xs: t.xs, ys: t.ys |> E.A.fmap(y)}; let fmap = (t: t, y): t => {xs: t.xs, ys: t.ys |> E.A.fmap(y)};
let scaleCdfTo = (~scaleTo=1., t: t) =>
switch (_lastElement(t.ys)) {
| Some(n) =>
let scaleBy = scaleTo /. n;
fmap(t, r => r *. scaleBy);
| None => t
};
let yFold = (fn, t: t) => { let yFold = (fn, t: t) => {
E.A.fold_left(fn, 0., t.ys); E.A.fold_left(fn, 0., t.ys);
}; };
@ -28,75 +36,69 @@ module XYShape = {
let fromArray = ((xs, ys)): t => {xs, ys}; let fromArray = ((xs, ys)): t => {xs, ys};
let fromArrays = (xs, ys): t => {xs, ys}; let fromArrays = (xs, ys): t => {xs, ys};
let transverse = (fn, p: t) => { let _transverse = fn =>
let (xs, ys) = Belt.Array.reduce(_, [||], (items, (x, y)) =>
Belt.Array.zip(p.xs, p.ys)
->Belt.Array.reduce([||], (items, (x, y)) =>
switch (_lastElement(items)) { switch (_lastElement(items)) {
| Some((_, yLast)) => | Some((xLast, yLast)) =>
Js.log3(y, yLast, fn(y, yLast)); Belt.Array.concat(items, [|(x, fn(y, yLast))|])
Belt.Array.concat(items, [|(x, fn(y, yLast))|]);
| None => [|(x, y)|] | None => [|(x, y)|]
} }
) );
|> Belt.Array.unzip;
fromArrays(xs, ys); let _transverseShape = (fn, p: t) => {
Belt.Array.zip(p.xs, p.ys)
|> _transverse(fn)
|> Belt.Array.unzip
|> fromArray;
}; };
let accumulateYs = _transverseShape((aCurrent, aLast) => aCurrent +. aLast);
let subtractYs = _transverseShape((aCurrent, aLast) => aCurrent -. aLast);
module Range = {
// ((lastX, lastY), (nextX, nextY))
type zippedRange = ((float, float), (float, float)); type zippedRange = ((float, float), (float, float));
let inRanges = (fn, t: t) => { let floatSum = Belt.Array.reduce(_, 0., (a, b) => a +. b);
let ranges: Belt.Result.t(array(zippedRange), string) = let toT = r => r |> Belt.Array.unzip |> fromArray;
Belt.Array.zip(t.xs, t.ys) |> E.A.toRanges; let nextX = ((_, (nextX, _)): zippedRange) => nextX;
ranges |> E.R.toOption |> E.O.fmap(fn);
};
let sum = Belt.Array.reduce(_, 0., (a, b) => a +. b); let rangeAreaAssumingSteps =
(((lastX, lastY), (nextX, _)): zippedRange) =>
let volume = {
let assumeLastY = (((lastX, lastY), (nextX, _)): zippedRange) =>
(nextX -. lastX) *. lastY; (nextX -. lastX) *. lastY;
inRanges((inRanges: array(zippedRange)) => let rangeAreaAssumingTriangles =
Belt.Array.map(inRanges, assumeLastY) |> sum (((lastX, lastY), (nextX, nextY)): zippedRange) =>
); (nextX -. lastX) *. (lastY +. nextY) /. 2.;
let delta_y_over_delta_x =
(((lastX, lastY), (nextX, nextY)): zippedRange) =>
(nextY -. lastY) /. (nextX -. lastX);
let inRanges = (mapper, reducer, t: t) => {
Belt.Array.zip(t.xs, t.ys)
|> E.A.toRanges
|> E.R.toOption
|> E.O.fmap(r => r |> Belt.Array.map(_, mapper) |> reducer);
}; };
let volumeTriangle = { let mapYsBasedOnRanges = fn => inRanges(r => (nextX(r), fn(r)), toT);
let assumeLastY = (((lastX, lastY), (nextX, nextY)): zippedRange) =>
(nextX -. lastX) *. (lastY -. nextY) /. 2.;
inRanges((inRanges: array(zippedRange)) => let toStepFn = z => mapYsBasedOnRanges(rangeAreaAssumingSteps, z);
Belt.Array.map(inRanges, assumeLastY) |> sum
); let integrateWithSteps = z =>
mapYsBasedOnRanges(rangeAreaAssumingSteps, z) |> E.O.fmap(accumulateYs);
let integrateWithTriangles = z =>
mapYsBasedOnRanges(rangeAreaAssumingTriangles, z)
|> E.O.fmap(accumulateYs);
let derivative = mapYsBasedOnRanges(delta_y_over_delta_x);
}; };
let volum2 = { let findY = CdfLibrary.Distribution.findY;
let assumeLastY = (((lastX, lastY), (nextX, _)): zippedRange) => ( let findX = CdfLibrary.Distribution.findX;
nextX,
(nextX -. lastX) *. lastY,
);
inRanges((inRanges: array(zippedRange)) =>
Belt.Array.map(inRanges, assumeLastY) |> Belt.Array.unzip |> fromArray
);
}; };
let diff = {
let assumeLastY = (((lastX, lastY), (nextX, _)): zippedRange) => (
nextX,
(lastY -. lastY) /. (nextX -. lastX),
);
inRanges((inRanges: array(zippedRange)) =>
Belt.Array.map(inRanges, assumeLastY) |> Belt.Array.unzip |> fromArray
);
};
let getY = (t: t, x: float) => x;
let findY = (t: t, x: float) => x;
let integral = transverse((aCurrent, aLast) => aCurrent +. aLast);
let derivative = transverse((aCurrent, aLast) => aCurrent -. aLast);
// let massWithin = (t: t, left: pointInRange, right: pointInRange) => { // let massWithin = (t: t, left: pointInRange, right: pointInRange) => {
// switch (left, right) { // switch (left, right) {
// | (Unbounded, Unbounded) => t |> ySum // | (Unbounded, Unbounded) => t |> ySum
@ -105,15 +107,15 @@ module XYShape = {
// | (X(l), X(r)) => getY(integral(t), r) -. getY(integral(t), l) // | (X(l), X(r)) => getY(integral(t), r) -. getY(integral(t), l)
// }; // };
// }; // };
};
module Continuous = { module Continuous = {
let fromArrays = XYShape.fromArrays; let fromArrays = XYShape.fromArrays;
let toJs = XYShape.toJs; let toJs = XYShape.toJs;
let toPdf = CdfLibrary.Distribution.toPdf; let toPdf = XYShape.Range.derivative;
let toCdf = CdfLibrary.Distribution.toCdf; let toCdf = XYShape.Range.integrateWithTriangles;
let findX = CdfLibrary.Distribution.findX; let findX = CdfLibrary.Distribution.findX;
let findY = CdfLibrary.Distribution.findY; let findY = CdfLibrary.Distribution.findY;
let findIntegralY = (f, r) => r |> toCdf |> E.O.fmap(findY(f));
}; };
module Discrete = { module Discrete = {
@ -145,6 +147,18 @@ module Discrete = {
| Some((_, y)) => y | Some((_, y)) => y
| None => 0. | None => 0.
}; };
let integrate = XYShape.accumulateYs;
let derivative = XYShape.subtractYs;
let findIntegralY = (f, t: t) =>
t
|> XYShape.Range.toStepFn
|> E.O.fmap(XYShape.accumulateYs)
|> E.O.fmap(CdfLibrary.Distribution.findY(f));
let findX = (f, t: t) =>
t |> XYShape.Range.toStepFn |> E.O.fmap(CdfLibrary.Distribution.findX(f));
}; };
module Mixed = { module Mixed = {
@ -154,14 +168,36 @@ module Mixed = {
discreteProbabilityMassFraction, discreteProbabilityMassFraction,
}; };
let mixedMultiply =
(
t: DistributionTypes.mixedShape,
continuousComponent,
discreteComponent,
) => {
let diffFn = t.discreteProbabilityMassFraction;
continuousComponent *. (1.0 -. diffFn) +. discreteComponent *. diffFn;
};
type yPdfPoint = { type yPdfPoint = {
continuous: float, continuous: option(float),
discrete: float, discrete: option(float),
discreteProbabilityMassFraction: float,
}; };
let getY = (t: DistributionTypes.mixedShape, x: float): yPdfPoint => { let getY = (t: DistributionTypes.mixedShape, x: float): yPdfPoint => {
continuous: Continuous.findY(x, t.continuous), continuous: Continuous.findY(x, t.continuous) |> E.O.some,
discrete: Discrete.findY(x, t.discrete), discrete: Discrete.findY(x, t.discrete) |> E.O.some,
discreteProbabilityMassFraction: t.discreteProbabilityMassFraction,
};
let getYIntegral =
(t: DistributionTypes.mixedShape, x: float): option(float) => {
let c = t.continuous |> Continuous.findIntegralY(x);
let d = Discrete.findIntegralY(x, t.discrete);
switch (c, d) {
| (Some(c), Some(d)) => Some(mixedMultiply(t, c, d))
| _ => None
};
}; };
}; };
@ -175,13 +211,6 @@ module Any = {
| Continuous(continuousShape) => | Continuous(continuousShape) =>
`continuous(Continuous.findY(x, continuousShape)) `continuous(Continuous.findY(x, continuousShape))
}; };
let massInRange = (t: t, left: pointInRange, right: pointInRange) =>
switch (t) {
| Mixed(m) => 3.0
| Discrete(discreteShape) => 2.0
| Continuous(continuousShape) => 3.0
};
}; };
module DomainMixed = { module DomainMixed = {