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}))
|
||||
: 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 logy = log(p /. (1. -. p))
|
||||
E.A.Floats.sum(Js.Array.mapi((term, i) =>
|
||||
|
@ -280,6 +283,11 @@ module Metalog = {
|
|||
, 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 k = Belt.Int.toFloat(i) +. 1.
|
||||
if i == 0 {
|
||||
|
@ -299,18 +307,9 @@ module Metalog = {
|
|||
}
|
||||
}, t.terms))
|
||||
|
||||
let rec improveCdfGuess = (guess: float, roundsLeft: int, target: float, t: t) =>
|
||||
if roundsLeft == 0 {
|
||||
guess
|
||||
} else {
|
||||
improveCdfGuess(
|
||||
guess -. (inv(guess, t) -. target) /. invderiv(guess, t),
|
||||
roundsLeft - 1,
|
||||
target,
|
||||
t,
|
||||
)
|
||||
}
|
||||
|
||||
// Simply approximates cdf values by bisection on the inv function over 50 rounds, starting at
|
||||
// a guess of 0.5. This use to be newton's method to approximate the CDF. However,
|
||||
// I found that this is as easy if not faster and easier to understand.
|
||||
let cdf = (x: float, t: t): float => {
|
||||
let guess = ref(0.5)
|
||||
let bisection_rounds = 50
|
||||
|
@ -325,6 +324,10 @@ module Metalog = {
|
|||
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 sample = (t: t) => inv(Jstat.Uniform.sample(0., 1.), t)
|
||||
|
@ -332,8 +335,10 @@ module Metalog = {
|
|||
let toString = ({terms}: t) =>
|
||||
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 meanSteps = 1000
|
||||
let stepCountFloat = Belt.Int.toFloat(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)))
|
||||
|
|
Loading…
Reference in New Issue
Block a user