Add comments about metalog distribution math
This commit is contained in:
parent
2adaba7d91
commit
594e6504ca
|
@ -261,6 +261,9 @@ module Metalog = {
|
||||||
? Ok(#Metalog({terms: terms}))
|
? Ok(#Metalog({terms: terms}))
|
||||||
: Error("Metalog must have 2 or more terms")
|
: Error("Metalog must have 2 or more terms")
|
||||||
|
|
||||||
|
// The flagship of the Metalog distribution. This is taken from:
|
||||||
|
// http://www.metalogdistributions.com/equations/unboundedmetalog.html
|
||||||
|
// keep in mind that in JS, arrays are 0 indexed, so i = k - 1
|
||||||
let inv = (p, t: t) => {
|
let inv = (p, t: t) => {
|
||||||
let logy = log(p /. (1. -. p))
|
let logy = log(p /. (1. -. p))
|
||||||
E.A.Floats.sum(Js.Array.mapi((term, i) =>
|
E.A.Floats.sum(Js.Array.mapi((term, i) =>
|
||||||
|
@ -280,6 +283,11 @@ module Metalog = {
|
||||||
, t.terms))
|
, t.terms))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The inverse quantile density function, the derivative of above. Taken from:
|
||||||
|
// http://www.metalogdistributions.com/equations/unboundedmetalog.html
|
||||||
|
// Keep in mind here that people often find the reciprocal of this value. I don't
|
||||||
|
// because it's easier to code this way and then just find the reciprocal at
|
||||||
|
// the very end
|
||||||
let invderiv = (y: float, t: t): float => E.A.Floats.sum(Js.Array.mapi((term, i) => {
|
let invderiv = (y: float, t: t): float => E.A.Floats.sum(Js.Array.mapi((term, i) => {
|
||||||
let k = Belt.Int.toFloat(i) +. 1.
|
let k = Belt.Int.toFloat(i) +. 1.
|
||||||
if i == 0 {
|
if i == 0 {
|
||||||
|
@ -299,18 +307,9 @@ module Metalog = {
|
||||||
}
|
}
|
||||||
}, t.terms))
|
}, t.terms))
|
||||||
|
|
||||||
let rec improveCdfGuess = (guess: float, roundsLeft: int, target: float, t: t) =>
|
// Simply approximates cdf values by bisection on the inv function over 50 rounds, starting at
|
||||||
if roundsLeft == 0 {
|
// a guess of 0.5. This use to be newton's method to approximate the CDF. However,
|
||||||
guess
|
// I found that this is as easy if not faster and easier to understand.
|
||||||
} else {
|
|
||||||
improveCdfGuess(
|
|
||||||
guess -. (inv(guess, t) -. target) /. invderiv(guess, t),
|
|
||||||
roundsLeft - 1,
|
|
||||||
target,
|
|
||||||
t,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let cdf = (x: float, t: t): float => {
|
let cdf = (x: float, t: t): float => {
|
||||||
let guess = ref(0.5)
|
let guess = ref(0.5)
|
||||||
let bisection_rounds = 50
|
let bisection_rounds = 50
|
||||||
|
@ -325,6 +324,10 @@ module Metalog = {
|
||||||
guess.contents
|
guess.contents
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The pdf first gets the cdf at x, then finds the density of that percentile
|
||||||
|
// See the notes at the bottom of this:
|
||||||
|
// http://www.metalogdistributions.com/equations/unboundedmetalog.html
|
||||||
|
// to see the logic behind this
|
||||||
let pdf = (x: float, t: t): float => 1. /. invderiv(cdf(x, t), t)
|
let pdf = (x: float, t: t): float => 1. /. invderiv(cdf(x, t), t)
|
||||||
|
|
||||||
let sample = (t: t) => inv(Jstat.Uniform.sample(0., 1.), t)
|
let sample = (t: t) => inv(Jstat.Uniform.sample(0., 1.), t)
|
||||||
|
@ -332,8 +335,10 @@ module Metalog = {
|
||||||
let toString = ({terms}: t) =>
|
let toString = ({terms}: t) =>
|
||||||
j`Metalog([${Js.Array.joinWith(", ", Js.Array.map(Belt.Float.toString, terms))}])`
|
j`Metalog([${Js.Array.joinWith(", ", Js.Array.map(Belt.Float.toString, terms))}])`
|
||||||
|
|
||||||
let meanSteps = 1000
|
// The mean of a metalog is simply the total integral of the inverse function.
|
||||||
|
// Discussed here: http://www.metalogdistributions.com/moments.html
|
||||||
let mean = (t: t) => {
|
let mean = (t: t) => {
|
||||||
|
let meanSteps = 1000
|
||||||
let stepCountFloat = Belt.Int.toFloat(meanSteps)
|
let stepCountFloat = Belt.Int.toFloat(meanSteps)
|
||||||
let range = E.A.Floats.range(0. +. 1. /. stepCountFloat, 1. -. 1. /. stepCountFloat, meanSteps)
|
let range = E.A.Floats.range(0. +. 1. /. stepCountFloat, 1. -. 1. /. stepCountFloat, meanSteps)
|
||||||
Ok(E.A.Floats.sum(E.A.fmap(x => inv(x, t) /. stepCountFloat, range)))
|
Ok(E.A.Floats.sum(E.A.fmap(x => inv(x, t) /. stepCountFloat, range)))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user