Merge pull request #440 from quantified-uncertainty/fromsamples
`fromSamples`
This commit is contained in:
commit
9464aea6fa
|
@ -1,5 +1,5 @@
|
||||||
import { Distribution } from "../../src/js/index";
|
import { Distribution } from "../../src/js/index";
|
||||||
import { expectErrorToBeBounded, failDefault } from "./TestHelpers";
|
import { expectErrorToBeBounded, failDefault, testRun } from "./TestHelpers";
|
||||||
import * as fc from "fast-check";
|
import * as fc from "fast-check";
|
||||||
|
|
||||||
// Beware: float64Array makes it appear in an infinite loop.
|
// Beware: float64Array makes it appear in an infinite loop.
|
||||||
|
@ -212,3 +212,18 @@ describe("mean is mean", () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("fromSamples function", () => {
|
||||||
|
test.skip("gives a mean near the mean of the input", () => {
|
||||||
|
fc.assert(
|
||||||
|
fc.property(arrayGen(), (xs_) => {
|
||||||
|
let xs = Array.from(xs_);
|
||||||
|
let xsString = xs.toString();
|
||||||
|
let squiggleString = `x = fromSamples([${xsString}]); mean(x)`;
|
||||||
|
let squiggleResult = testRun(squiggleString);
|
||||||
|
let mean = xs.reduce((a, b) => a + b, 0.0) / xs.length;
|
||||||
|
expect(squiggleResult.value).toBeCloseTo(mean, 4);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -189,6 +189,11 @@ let rec run = (~env, functionCallInfo: functionCallInfo): outputType => {
|
||||||
->GenericDist.mixture(~scaleMultiplyFn=scaleMultiply, ~pointwiseAddFn=pointwiseAdd)
|
->GenericDist.mixture(~scaleMultiplyFn=scaleMultiply, ~pointwiseAddFn=pointwiseAdd)
|
||||||
->E.R2.fmap(r => Dist(r))
|
->E.R2.fmap(r => Dist(r))
|
||||||
->OutputLocal.fromResult
|
->OutputLocal.fromResult
|
||||||
|
| FromSamples(xs) => xs
|
||||||
|
->SampleSetDist.make
|
||||||
|
->E.R2.errMap(x => DistributionTypes.SampleSetError(x))
|
||||||
|
->E.R2.fmap(x => x->DistributionTypes.SampleSet->Dist)
|
||||||
|
->OutputLocal.fromResult
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,6 +234,7 @@ module Constructors = {
|
||||||
let isNormalized = (~env, dist) => C.isNormalized(dist)->run(~env)->toBoolR
|
let isNormalized = (~env, dist) => C.isNormalized(dist)->run(~env)->toBoolR
|
||||||
let toPointSet = (~env, dist) => C.toPointSet(dist)->run(~env)->toDistR
|
let toPointSet = (~env, dist) => C.toPointSet(dist)->run(~env)->toDistR
|
||||||
let toSampleSet = (~env, dist, n) => C.toSampleSet(dist, n)->run(~env)->toDistR
|
let toSampleSet = (~env, dist, n) => C.toSampleSet(dist, n)->run(~env)->toDistR
|
||||||
|
let fromSamples = (~env, xs) => C.fromSamples(xs)->run(~env)->toDistR
|
||||||
let truncate = (~env, dist, leftCutoff, rightCutoff) =>
|
let truncate = (~env, dist, leftCutoff, rightCutoff) =>
|
||||||
C.truncate(dist, leftCutoff, rightCutoff)->run(~env)->toDistR
|
C.truncate(dist, leftCutoff, rightCutoff)->run(~env)->toDistR
|
||||||
let inspect = (~env, dist) => C.inspect(dist)->run(~env)->toDistR
|
let inspect = (~env, dist) => C.inspect(dist)->run(~env)->toDistR
|
||||||
|
|
|
@ -61,6 +61,8 @@ module Constructors: {
|
||||||
@genType
|
@genType
|
||||||
let toSampleSet: (~env: env, genericDist, int) => result<genericDist, error>
|
let toSampleSet: (~env: env, genericDist, int) => result<genericDist, error>
|
||||||
@genType
|
@genType
|
||||||
|
let fromSamples: (~env: env, SampleSetDist.t) => result<genericDist, error>
|
||||||
|
@genType
|
||||||
let truncate: (~env: env, genericDist, option<float>, option<float>) => result<genericDist, error>
|
let truncate: (~env: env, genericDist, option<float>, option<float>) => result<genericDist, error>
|
||||||
@genType
|
@genType
|
||||||
let inspect: (~env: env, genericDist) => result<genericDist, error>
|
let inspect: (~env: env, genericDist) => result<genericDist, error>
|
||||||
|
|
|
@ -11,7 +11,7 @@ type error =
|
||||||
| NotYetImplemented
|
| NotYetImplemented
|
||||||
| Unreachable
|
| Unreachable
|
||||||
| DistributionVerticalShiftIsInvalid
|
| DistributionVerticalShiftIsInvalid
|
||||||
| TooFewSamples
|
| SampleSetError(SampleSetDist.sampleSetError)
|
||||||
| ArgumentError(string)
|
| ArgumentError(string)
|
||||||
| OperationError(Operation.Error.t)
|
| OperationError(Operation.Error.t)
|
||||||
| PointSetConversionError(SampleSetDist.pointsetConversionError)
|
| PointSetConversionError(SampleSetDist.pointsetConversionError)
|
||||||
|
@ -35,7 +35,8 @@ module Error = {
|
||||||
| DistributionVerticalShiftIsInvalid => "Distribution Vertical Shift is Invalid"
|
| DistributionVerticalShiftIsInvalid => "Distribution Vertical Shift is Invalid"
|
||||||
| ArgumentError(s) => `Argument Error ${s}`
|
| ArgumentError(s) => `Argument Error ${s}`
|
||||||
| LogarithmOfDistributionError(s) => `Logarithm of input error: ${s}`
|
| LogarithmOfDistributionError(s) => `Logarithm of input error: ${s}`
|
||||||
| TooFewSamples => "Too Few Samples"
|
| SampleSetError(TooFewSamples) => "Too Few Samples"
|
||||||
|
| SampleSetError(NonNumericInput(err)) => `Found a non-number in input: ${err}`
|
||||||
| OperationError(err) => Operation.Error.toString(err)
|
| OperationError(err) => Operation.Error.toString(err)
|
||||||
| PointSetConversionError(err) => SampleSetDist.pointsetConversionErrorToString(err)
|
| PointSetConversionError(err) => SampleSetDist.pointsetConversionErrorToString(err)
|
||||||
| SparklineError(err) => PointSetTypes.sparklineErrorToString(err)
|
| SparklineError(err) => PointSetTypes.sparklineErrorToString(err)
|
||||||
|
@ -47,10 +48,7 @@ module Error = {
|
||||||
let resultStringToResultError: result<'a, string> => result<'a, error> = n =>
|
let resultStringToResultError: result<'a, string> => result<'a, error> = n =>
|
||||||
n->E.R2.errMap(r => r->fromString)
|
n->E.R2.errMap(r => r->fromString)
|
||||||
|
|
||||||
let sampleErrorToDistErr = (err: SampleSetDist.sampleSetError): error =>
|
let sampleErrorToDistErr = (err: SampleSetDist.sampleSetError): error => SampleSetError(err)
|
||||||
switch err {
|
|
||||||
| TooFewSamples => TooFewSamples
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@genType
|
@genType
|
||||||
|
@ -99,6 +97,7 @@ module DistributionOperation = {
|
||||||
type genericFunctionCallInfo =
|
type genericFunctionCallInfo =
|
||||||
| FromDist(fromDist, genericDist)
|
| FromDist(fromDist, genericDist)
|
||||||
| FromFloat(fromDist, float)
|
| FromFloat(fromDist, float)
|
||||||
|
| FromSamples(array<float>)
|
||||||
| Mixture(array<(genericDist, float)>)
|
| Mixture(array<(genericDist, float)>)
|
||||||
|
|
||||||
let distCallToString = (distFunction: fromDist): string =>
|
let distCallToString = (distFunction: fromDist): string =>
|
||||||
|
@ -124,6 +123,7 @@ module DistributionOperation = {
|
||||||
switch d {
|
switch d {
|
||||||
| FromDist(f, _) | FromFloat(f, _) => distCallToString(f)
|
| FromDist(f, _) | FromFloat(f, _) => distCallToString(f)
|
||||||
| Mixture(_) => `mixture`
|
| Mixture(_) => `mixture`
|
||||||
|
| FromSamples(_) => `fromSamples`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
module Constructors = {
|
module Constructors = {
|
||||||
|
@ -140,6 +140,7 @@ module Constructors = {
|
||||||
let isNormalized = (dist): t => FromDist(ToBool(IsNormalized), dist)
|
let isNormalized = (dist): t => FromDist(ToBool(IsNormalized), dist)
|
||||||
let toPointSet = (dist): t => FromDist(ToDist(ToPointSet), dist)
|
let toPointSet = (dist): t => FromDist(ToDist(ToPointSet), dist)
|
||||||
let toSampleSet = (dist, r): t => FromDist(ToDist(ToSampleSet(r)), dist)
|
let toSampleSet = (dist, r): t => FromDist(ToDist(ToSampleSet(r)), dist)
|
||||||
|
let fromSamples = (xs): t => FromSamples(xs)
|
||||||
let truncate = (dist, left, right): t => FromDist(ToDist(Truncate(left, right)), dist)
|
let truncate = (dist, left, right): t => FromDist(ToDist(Truncate(left, right)), dist)
|
||||||
let inspect = (dist): t => FromDist(ToDist(Inspect), dist)
|
let inspect = (dist): t => FromDist(ToDist(Inspect), dist)
|
||||||
let toString = (dist): t => FromDist(ToString(ToString), dist)
|
let toString = (dist): t => FromDist(ToString(ToString), dist)
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
@genType
|
@genType
|
||||||
module Error = {
|
module Error = {
|
||||||
@genType
|
@genType
|
||||||
type sampleSetError = TooFewSamples
|
type sampleSetError = TooFewSamples | NonNumericInput(string)
|
||||||
|
|
||||||
let sampleSetErrorToString = (err: sampleSetError): string =>
|
let sampleSetErrorToString = (err: sampleSetError): string =>
|
||||||
switch err {
|
switch err {
|
||||||
| TooFewSamples => "Too few samples when constructing sample set"
|
| TooFewSamples => "Too few samples when constructing sample set"
|
||||||
|
| NonNumericInput(err) => `Found a non-number in input: ${err}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@genType
|
@genType
|
||||||
|
|
|
@ -1,27 +1,30 @@
|
||||||
//The math here was taken from https://github.com/jasondavies/science.js/blob/master/src/stats/SampleSetDist_Bandwidth.js
|
//The math here was taken from https://github.com/jasondavies/science.js/blob/master/src/stats/SampleSetDist_Bandwidth.js
|
||||||
|
let {iqr_percentile, nrd0_lo_denominator, one, nrd0_coef, nrd_coef, nrd_fractionalPower} = module(
|
||||||
|
MagicNumbers.SampleSetBandwidth
|
||||||
|
)
|
||||||
let len = x => E.A.length(x) |> float_of_int
|
let len = x => E.A.length(x) |> float_of_int
|
||||||
|
|
||||||
let iqr = x => Jstat.percentile(x, 0.75, true) -. Jstat.percentile(x, 0.25, true)
|
let iqr = x =>
|
||||||
|
Jstat.percentile(x, iqr_percentile, true) -. Jstat.percentile(x, 1.0 -. iqr_percentile, true)
|
||||||
|
|
||||||
// Silverman, B. W. (1986) Density Estimation. London: Chapman and Hall.
|
// Silverman, B. W. (1986) Density Estimation. London: Chapman and Hall.
|
||||||
let nrd0 = x => {
|
let nrd0 = x => {
|
||||||
let hi = Js_math.sqrt(Jstat.variance(x))
|
let hi = Js_math.sqrt(Jstat.variance(x))
|
||||||
let lo = Js_math.minMany_float([hi, iqr(x) /. 1.34])
|
let lo = Js_math.minMany_float([hi, iqr(x) /. nrd0_lo_denominator])
|
||||||
let e = Js_math.abs_float(x[1])
|
let e = Js_math.abs_float(x[1])
|
||||||
let lo' = switch (lo, hi, e) {
|
let lo' = switch (lo, hi, e) {
|
||||||
| (lo, _, _) if !Js.Float.isNaN(lo) => lo
|
| (lo, _, _) if !Js.Float.isNaN(lo) => lo
|
||||||
| (_, hi, _) if !Js.Float.isNaN(hi) => hi
|
| (_, hi, _) if !Js.Float.isNaN(hi) => hi
|
||||||
| (_, _, e) if !Js.Float.isNaN(e) => e
|
| (_, _, e) if !Js.Float.isNaN(e) => e
|
||||||
| _ => 1.0
|
| _ => one
|
||||||
}
|
}
|
||||||
0.9 *. lo' *. Js.Math.pow_float(~base=len(x), ~exp=-0.2)
|
nrd0_coef *. lo' *. Js.Math.pow_float(~base=len(x), ~exp=nrd_fractionalPower)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scott, D. W. (1992) Multivariate Density Estimation: Theory, Practice, and Visualization. Wiley.
|
// Scott, D. W. (1992) Multivariate Density Estimation: Theory, Practice, and Visualization. Wiley.
|
||||||
let nrd = x => {
|
let nrd = x => {
|
||||||
let h = iqr(x) /. 1.34
|
let h = iqr(x) /. nrd0_lo_denominator
|
||||||
1.06 *.
|
nrd_coef *.
|
||||||
Js.Math.min_float(Js.Math.sqrt(Jstat.variance(x)), h) *.
|
Js.Math.min_float(Js.Math.sqrt(Jstat.variance(x)), h) *.
|
||||||
Js.Math.pow_float(~base=len(x), ~exp=-1.0 /. 5.0)
|
Js.Math.pow_float(~base=len(x), ~exp=nrd_fractionalPower)
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,3 +35,16 @@ module ToPointSet = {
|
||||||
*/
|
*/
|
||||||
let minDiscreteToKeep = samples => max(20, E.A.length(samples) / 50)
|
let minDiscreteToKeep = samples => max(20, E.A.length(samples) / 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module SampleSetBandwidth = {
|
||||||
|
// Silverman, B. W. (1986) Density Estimation. London: Chapman and Hall.
|
||||||
|
// Scott, D. W. (1992) Multivariate Density Estimation: Theory, Practice, and Visualization. Wiley.
|
||||||
|
let iqr_percentile = 0.75
|
||||||
|
let iqr_percentile_complement = 1.0 -. iqr_percentile
|
||||||
|
let nrd0_lo_denominator = 1.34
|
||||||
|
let one = 1.0
|
||||||
|
let nrd0_coef = 0.9
|
||||||
|
|
||||||
|
let nrd_coef = 1.06
|
||||||
|
let nrd_fractionalPower = -0.2
|
||||||
|
}
|
||||||
|
|
|
@ -218,6 +218,14 @@ let dispatchToGenericOutput = (call: ExpressionValue.functionCall): option<
|
||||||
Helpers.toDistFn(ToSampleSet(Belt.Int.fromFloat(float)), dist)
|
Helpers.toDistFn(ToSampleSet(Belt.Int.fromFloat(float)), dist)
|
||||||
| ("toSampleSet", [EvDistribution(dist)]) =>
|
| ("toSampleSet", [EvDistribution(dist)]) =>
|
||||||
Helpers.toDistFn(ToSampleSet(MagicNumbers.Environment.defaultSampleCount), dist)
|
Helpers.toDistFn(ToSampleSet(MagicNumbers.Environment.defaultSampleCount), dist)
|
||||||
|
| ("fromSamples", [EvArray(inputArray)]) => {
|
||||||
|
let _wrapInputErrors = x => SampleSetDist.NonNumericInput(x)
|
||||||
|
let parsedArray = Helpers.parseNumberArray(inputArray)->E.R2.errMap(_wrapInputErrors)
|
||||||
|
switch parsedArray {
|
||||||
|
| Ok(array) => runGenericOperation(FromSamples(array))
|
||||||
|
| Error(e) => GenDistError(SampleSetError(e))
|
||||||
|
}->Some
|
||||||
|
}
|
||||||
| ("inspect", [EvDistribution(dist)]) => Helpers.toDistFn(Inspect, dist)
|
| ("inspect", [EvDistribution(dist)]) => Helpers.toDistFn(Inspect, dist)
|
||||||
| ("truncateLeft", [EvDistribution(dist), EvNumber(float)]) =>
|
| ("truncateLeft", [EvDistribution(dist), EvNumber(float)]) =>
|
||||||
Helpers.toDistFn(Truncate(Some(float), None), dist)
|
Helpers.toDistFn(Truncate(Some(float), None), dist)
|
||||||
|
|
|
@ -289,6 +289,13 @@ module R = {
|
||||||
| Ok(r) => r->Ok
|
| Ok(r) => r->Ok
|
||||||
| Error(x) => x->f->Error
|
| Error(x) => x->f->Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//I'm not sure what to call this.
|
||||||
|
let unify = (a: result<'a, 'b>, c: 'b => 'a): 'a =>
|
||||||
|
switch a {
|
||||||
|
| Ok(x) => x
|
||||||
|
| Error(x) => c(x)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module R2 = {
|
module R2 = {
|
||||||
|
@ -307,6 +314,8 @@ module R2 = {
|
||||||
| Ok(x) => x->Ok
|
| Ok(x) => x->Ok
|
||||||
| Error(x) => x->f->Error
|
| Error(x) => x->f->Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let toExn = (a, b) => R.toExn(b, a)
|
||||||
}
|
}
|
||||||
|
|
||||||
let safe_fn_of_string = (fn, s: string): option<'a> =>
|
let safe_fn_of_string = (fn, s: string): option<'a> =>
|
||||||
|
|
|
@ -98,6 +98,19 @@ bound `a`, mode `b` and upper bound `c`.
|
||||||
|
|
||||||
Squiggle, when the context is right, automatically casts a float to a constant distribution.
|
Squiggle, when the context is right, automatically casts a float to a constant distribution.
|
||||||
|
|
||||||
|
## `fromSamples`
|
||||||
|
|
||||||
|
The last distribution constructor takes an array of samples and constructs a sample set distribution.
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="fromSamples([1,2,3,4,6,5,5,5])" />
|
||||||
|
|
||||||
|
#### Validity
|
||||||
|
|
||||||
|
For `fromSamples(xs)`,
|
||||||
|
|
||||||
|
- `xs.length > 5`
|
||||||
|
- Strictly every element of `xs` must be a number.
|
||||||
|
|
||||||
## Operating on distributions
|
## Operating on distributions
|
||||||
|
|
||||||
Here are the ways we combine distributions.
|
Here are the ways we combine distributions.
|
||||||
|
@ -315,6 +328,16 @@ Or `PointSet` format
|
||||||
|
|
||||||
<SquiggleEditor initialSquiggleString="toPointSet(normal(5, 10))" />
|
<SquiggleEditor initialSquiggleString="toPointSet(normal(5, 10))" />
|
||||||
|
|
||||||
|
### `toSampleSet` has two signatures
|
||||||
|
|
||||||
|
Above, we saw the unary `toSampleSet`, which uses an internal hardcoded number of samples. If you'd like to provide the number of samples, it has a binary signature as well (floored)
|
||||||
|
|
||||||
|
<SquiggleEditor initialSquiggleString="toSampleSet(0.1 to 1, 100.1)" />
|
||||||
|
|
||||||
|
#### Validity
|
||||||
|
|
||||||
|
- Second argument to `toSampleSet` must be a number.
|
||||||
|
|
||||||
## Normalization
|
## Normalization
|
||||||
|
|
||||||
Some distribution operations (like horizontal shift) return an unnormalized distriibution.
|
Some distribution operations (like horizontal shift) return an unnormalized distriibution.
|
||||||
|
@ -333,18 +356,6 @@ We provide a predicate `isNormalized`, for when we have simple control flow
|
||||||
|
|
||||||
- Input to `isNormalized` must be a dist
|
- Input to `isNormalized` must be a dist
|
||||||
|
|
||||||
## Convert any distribution to a sample set distribution
|
|
||||||
|
|
||||||
`toSampleSet` has two signatures
|
|
||||||
|
|
||||||
It is unary when you use an internal hardcoded number of samples
|
|
||||||
|
|
||||||
<SquiggleEditor initialSquiggleString="toSampleSet(0.1 to 1)" />
|
|
||||||
|
|
||||||
And binary when you provide a number of samples (floored)
|
|
||||||
|
|
||||||
<SquiggleEditor initialSquiggleString="toSampleSet(0.1 to 1, 100)" />
|
|
||||||
|
|
||||||
## `inspect`
|
## `inspect`
|
||||||
|
|
||||||
You may like to debug by right clicking your browser and using the _inspect_ functionality on the webpage, and viewing the _console_ tab. Then, wrap your squiggle output with `inspect` to log an internal representation.
|
You may like to debug by right clicking your browser and using the _inspect_ functionality on the webpage, and viewing the _console_ tab. Then, wrap your squiggle output with `inspect` to log an internal representation.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user