Math.NET Numerics


Probability Distributions

Math.NET Numerics provides a wide range of probability distributions. Given the distribution parameters they can be used to investigate their statistical properties or to sample non-uniform random numbers.

All the distributions implement a common set of operations such as evaluating the density (PDF) and the cumulative distribution (CDF) at a given point, or to compute the mean, standard deviation and other properties. Because it is often numerically more stable and faster to compute such statistical quantities in the logarithmic domain, we also provide a selection of them in the log domain with the "Ln" suffix, e.g. DensityLn for the logarithmic density.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
using MathNet.Numerics.Distributions;
using MathNet.Numerics.Random;

// create a parametrized distribution instance
var gamma = new Gamma(2.0, 1.5);

// distribution properties
double mean = gamma.Mean;
double variance = gamma.Variance;
double entropy = gamma.Entropy;

// distribution functions
double a = gamma.Density(2.3); // PDF
double b = gamma.DensityLn(2.3); // ln(PDF)
double c = gamma.CumulativeDistribution(0.7); // CDF

// non-uniform number sampling
double randomSample = gamma.Sample();

Both probability functions and sampling are also available as static functions for simpler usage scenarios:

1: 
2: 
3: 
// distribution parameters must be passed as arguments
double a2 = Gamma.PDF(2.0, 1.5, 2.3);
double randomSample2 = Gamma.Sample(2.0, 1.5);

Continuous Distributions

Discrete Distributions

Multivariate Distributions

Distribution Parameters

There are many ways to parametrize a distribution in the literature. When using the default constructor, read carefully which parameters it requires. For distributions where multiple ways are common there are also static methods, so you can use the one that fits best. For example, a normal distribution is usually parametrized with mean and standard deviation, but if you'd rather use mean and precision:

1: 
var normal = Normal.WithMeanPrecision(0.0, 0.5);

Since probability distributions can also be sampled to generate random numbers with the configured distribution, all constructors optionally accept a random generator as last argument.

1: 
2: 
3: 
4: 
var gamma2 = new Gamma(2.0, 1.5, new MersenneTwister());

// the random generator can also be replaced on an existing instance
gamma2.RandomSource = new Mrg32k3a();

A few more examples, this time in F#:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
// some probability distributions
let normal = Normal.WithMeanVariance(3.0, 1.5, a)
let exponential = Exponential(2.4)
let gamma = Gamma(2.0, 1.5, Random.crypto())
let cauchy = Cauchy(0.0, 1.0, Random.mrg32k3aWith 10 false)
let poisson = Poisson(3.0)
let geometric = Geometric(0.8, Random.system())

Some of the distributions also have routines for maximum-likelihood parameter estimation from a set of samples:

1: 
2: 
3: 
let estimation = LogNormal.Estimate([| 2.0; 1.5; 2.1; 1.2; 3.0; 2.4; 1.8 |])
let mean, variance = estimation.Mean, estimation.Variance
let moreSamples = estimation.Samples() |> Seq.take 10 |> Seq.toArray

or in C#:

1: 
2: 
3: 
LogNormal estimation = LogNormal.Estimate(new [] {2.0, 1.5, 2.1, 1.2, 3.0, 2.4, 1.8});
double mean = estimation.Mean, variance = estimation.Variance;
double[] moreSamples = estimation.Samples().Take(10).ToArray();

Sampling a Probability Distribution

Each distribution provides methods to generate random numbers from that distribution. These random variate generators work by accessing the distribution's member RandomSource to provide uniform random numbers. By default, this member is an instance of System.Random but one can easily replace this with more sophisticated random number generators from MathNet.Numerics.Random (see Random Numbers for details).

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
// sample some random numbers from these distributions
// continuous distributions sample to floating-point numbers:
let continuous =
  [ yield normal.Sample()
    yield exponential.Sample()
    yield! gamma.Samples() |> Seq.take 10 ]

// discrete distributions on the other hand sample to integers:
let discrete =
  [ poisson.Sample()
    poisson.Sample()
    geometric.Sample() ]

Instead of creating a distribution object we can also sample directly with static functions. Note that no intermediate value caching is possible this way and parameters must be validated on each call.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
// using the default number generator (SystemRandomSource.Default)
let w = Rayleigh.Sample(1.5)
let x = Hypergeometric.Sample(100, 20, 5)

// or by manually providing the uniform random number generator
let u = Normal.Sample(Random.system(), 2.0, 4.0)
let v = Laplace.Samples(Random.mersenneTwister(), 1.0, 3.0) |> Seq.take 100 |> List.ofSeq

If you need to sample not just one or two values but a large number of them, there are routines that either fill an existing array or return an enumerable. The variant that fills an array is generally the fastest. Routines to sample more than one value use the plural form Samples instead of Sample.

Let's sample 100'000 values from a laplace distribution with mean 1.0 and scale 2.0 in C#:

1: 
2: 
var samples = new double[100000];
Laplace.Samples(samples, 1.0, 2.0);

Let's do some random walks in F# (TODO: Graph):

1: 
2: 
Seq.scan (+) 0.0 (Normal.Samples(0.0, 1.0)) |> Seq.take 10 |> Seq.toArray
Seq.scan (+) 0.0 (Cauchy.Samples(0.0, 1.0)) |> Seq.take 10 |> Seq.toArray

Distribution Functions and Properties

Distributions can not just be used to generate non-uniform random samples. Once parametrized they can compute a variety of distribution properties or evaluate distribution functions. Because it is often numerically more stable and faster to compute and work with such quantities in the logarithmic domain, some of them are also available with the Ln-suffix.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
// distribution properties of the gamma we've configured above
let gammaStats =
  ( gamma.Mean,
    gamma.Variance,
    gamma.StdDev,
    gamma.Entropy,
    gamma.Skewness,
    gamma.Mode )

// probability distribution functions of the normal we've configured above.
let nd = normal.Density(4.0)  (* PDF *)
let ndLn = normal.DensityLn(4.0)  (* ln(PDF) *)
let nc = normal.CumulativeDistribution(4.0)  (* CDF *)
let nic = normal.InverseCumulativeDistribution(0.7)  (* CDF^(-1) *)

// Distribution functions can also be evaluated without creating an object,
// but then you have to pass in the distribution parameters as first arguments:
let nd2 = Normal.PDF(3.0, sqrt 1.5, 4.0)
let ndLn2 = Normal.PDFLn(3.0, sqrt 1.5, 4.0)
let nc2 = Normal.CDF(3.0, sqrt 1.5, 4.0)
let nic2 = Normal.InvCDF(3.0, sqrt 1.5, 0.7)

Composing Distributions

Specifically for F# there is also a Sample module that allows a somewhat more functional view on distribution sampling functions by having the random source passed in as last argument. This way they can be composed and transformed arbitrarily if curried:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
/// Transform a sample from a distribution
let s1 rng = tanh (Sample.normal 2.0 0.5 rng)

/// But we really want to transform the function, not the resulting sample:
let s1f rng = Sample.map tanh (Sample.normal 2.0 0.5) rng

/// Exactly the same also works with functions generating full sequences
let s1s rng = Sample.mapSeq tanh (Sample.normalSeq 2.0 0.5) rng

/// Now with multiple distributions, e.g. their product:
let s2 rng = (Sample.normal 2.0 1.5 rng) * (Sample.cauchy 2.0 0.5 rng)
let s2f rng = Sample.map2 (*) (Sample.normal 2.0 1.5) (Sample.cauchy 2.0 0.5) rng
let s2s rng = Sample.mapSeq2 (*) (Sample.normalSeq 2.0 1.5) (Sample.cauchySeq 2.0 0.5) rng

// Taking some samples from the composed function
Seq.take 10 (s2s (Random.system())) |> Seq.toArray
val normal : Normal

Full name: Probability.normal
Multiple items
type Normal =
  new : unit -> Normal + 3 overloads
  member CumulativeDistribution : x:float -> float
  member Density : x:float -> float
  member DensityLn : x:float -> float
  member Entropy : float
  member InverseCumulativeDistribution : p:float -> float
  member Maximum : float
  member Mean : float
  member Median : float
  member Minimum : float
  ...

Full name: MathNet.Numerics.Distributions.Normal

--------------------
Normal() : unit
Normal(randomSource: System.Random) : unit
Normal(mean: float, stddev: float) : unit
Normal(mean: float, stddev: float, randomSource: System.Random) : unit
Normal.WithMeanVariance(mean: float, var: float, ?randomSource: System.Random) : Normal
val exponential : Exponential

Full name: Probability.exponential
Multiple items
type Exponential =
  new : rate:float -> Exponential + 1 overload
  member CumulativeDistribution : x:float -> float
  member Density : x:float -> float
  member DensityLn : x:float -> float
  member Entropy : float
  member InverseCumulativeDistribution : p:float -> float
  member Maximum : float
  member Mean : float
  member Median : float
  member Minimum : float
  ...

Full name: MathNet.Numerics.Distributions.Exponential

--------------------
Exponential(rate: float) : unit
Exponential(rate: float, randomSource: System.Random) : unit
val gamma : Gamma

Full name: Probability.gamma
Multiple items
type Gamma =
  new : shape:float * rate:float -> Gamma + 1 overload
  member CumulativeDistribution : x:float -> float
  member Density : x:float -> float
  member DensityLn : x:float -> float
  member Entropy : float
  member InverseCumulativeDistribution : p:float -> float
  member Maximum : float
  member Mean : float
  member Median : float
  member Minimum : float
  ...

Full name: MathNet.Numerics.Distributions.Gamma

--------------------
Gamma(shape: float, rate: float) : unit
Gamma(shape: float, rate: float, randomSource: System.Random) : unit
Multiple items
module Random

from MathNet.Numerics.Random

--------------------
namespace MathNet.Numerics.Random
val crypto : unit -> System.Random

Full name: MathNet.Numerics.Random.Random.crypto
val cauchy : Cauchy

Full name: Probability.cauchy
Multiple items
type Cauchy =
  new : unit -> Cauchy + 2 overloads
  member CumulativeDistribution : x:float -> float
  member Density : x:float -> float
  member DensityLn : x:float -> float
  member Entropy : float
  member InverseCumulativeDistribution : p:float -> float
  member Location : float
  member Maximum : float
  member Mean : float
  member Median : float
  ...

Full name: MathNet.Numerics.Distributions.Cauchy

--------------------
Cauchy() : unit
Cauchy(location: float, scale: float) : unit
Cauchy(location: float, scale: float, randomSource: System.Random) : unit
val mrg32k3aWith : seed:int -> threadSafe:bool -> System.Random

Full name: MathNet.Numerics.Random.Random.mrg32k3aWith
val poisson : Poisson

Full name: Probability.poisson
Multiple items
type Poisson =
  new : lambda:float -> Poisson + 1 overload
  member CumulativeDistribution : x:float -> float
  member Entropy : float
  member Lambda : float
  member Maximum : int
  member Mean : float
  member Median : float
  member Minimum : int
  member Mode : int
  member Probability : k:int -> float
  ...

Full name: MathNet.Numerics.Distributions.Poisson

--------------------
Poisson(lambda: float) : unit
Poisson(lambda: float, randomSource: System.Random) : unit
val geometric : Geometric

Full name: Probability.geometric
Multiple items
type Geometric =
  new : p:float -> Geometric + 1 overload
  member CumulativeDistribution : x:float -> float
  member Entropy : float
  member Maximum : int
  member Mean : float
  member Median : float
  member Minimum : int
  member Mode : int
  member P : float
  member Probability : k:int -> float
  ...

Full name: MathNet.Numerics.Distributions.Geometric

--------------------
Geometric(p: float) : unit
Geometric(p: float, randomSource: System.Random) : unit
val system : unit -> System.Random

Full name: MathNet.Numerics.Random.Random.system
val estimation : LogNormal

Full name: Probability.estimation
Multiple items
type LogNormal =
  new : mu:float * sigma:float -> LogNormal + 1 overload
  member CumulativeDistribution : x:float -> float
  member Density : x:float -> float
  member DensityLn : x:float -> float
  member Entropy : float
  member InverseCumulativeDistribution : p:float -> float
  member Maximum : float
  member Mean : float
  member Median : float
  member Minimum : float
  ...

Full name: MathNet.Numerics.Distributions.LogNormal

--------------------
LogNormal(mu: float, sigma: float) : unit
LogNormal(mu: float, sigma: float, randomSource: System.Random) : unit
LogNormal.Estimate(samples: System.Collections.Generic.IEnumerable<float>, ?randomSource: System.Random) : LogNormal
val mean : float

Full name: Probability.mean
val variance : float

Full name: Probability.variance
property LogNormal.Mean: float
property LogNormal.Variance: float
val moreSamples : float []

Full name: Probability.moreSamples
LogNormal.Samples() : System.Collections.Generic.IEnumerable<float>
LogNormal.Samples(values: float []) : unit
module Seq

from Microsoft.FSharp.Collections
val take : count:int -> source:seq<'T> -> seq<'T>

Full name: Microsoft.FSharp.Collections.Seq.take
val toArray : source:seq<'T> -> 'T []

Full name: Microsoft.FSharp.Collections.Seq.toArray
val continuous : float list

Full name: Probability.continuous
Normal.Sample() : float
Exponential.Sample() : float
Gamma.Samples() : System.Collections.Generic.IEnumerable<float>
Gamma.Samples(values: float []) : unit
val discrete : int list

Full name: Probability.discrete
Poisson.Sample() : int
Geometric.Sample() : int
val w : float

Full name: Probability.w
Multiple items
type Rayleigh =
  new : scale:float -> Rayleigh + 1 overload
  member CumulativeDistribution : x:float -> float
  member Density : x:float -> float
  member DensityLn : x:float -> float
  member Entropy : float
  member InverseCumulativeDistribution : p:float -> float
  member Maximum : float
  member Mean : float
  member Median : float
  member Minimum : float
  ...

Full name: MathNet.Numerics.Distributions.Rayleigh

--------------------
Rayleigh(scale: float) : unit
Rayleigh(scale: float, randomSource: System.Random) : unit
Rayleigh.Sample(scale: float) : float
Rayleigh.Sample(rnd: System.Random, scale: float) : float
val x : int

Full name: Probability.x
Multiple items
type Hypergeometric =
  new : population:int * success:int * draws:int -> Hypergeometric + 1 overload
  member CumulativeDistribution : x:float -> float
  member Draws : int
  member Entropy : float
  member Maximum : int
  member Mean : float
  member Median : float
  member Minimum : int
  member Mode : int
  member Population : int
  ...

Full name: MathNet.Numerics.Distributions.Hypergeometric

--------------------
Hypergeometric(population: int, success: int, draws: int) : unit
Hypergeometric(population: int, success: int, draws: int, randomSource: System.Random) : unit
Hypergeometric.Sample(population: int, success: int, draws: int) : int
Hypergeometric.Sample(rnd: System.Random, population: int, success: int, draws: int) : int
val u : float

Full name: Probability.u
Normal.Sample(mean: float, stddev: float) : float
Normal.Sample(rnd: System.Random, mean: float, stddev: float) : float
val v : float list

Full name: Probability.v
Multiple items
type Laplace =
  new : unit -> Laplace + 2 overloads
  member CumulativeDistribution : x:float -> float
  member Density : x:float -> float
  member DensityLn : x:float -> float
  member Entropy : float
  member Location : float
  member Maximum : float
  member Mean : float
  member Median : float
  member Minimum : float
  ...

Full name: MathNet.Numerics.Distributions.Laplace

--------------------
Laplace() : unit
Laplace(location: float, scale: float) : unit
Laplace(location: float, scale: float, randomSource: System.Random) : unit
Laplace.Samples(location: float, scale: float) : System.Collections.Generic.IEnumerable<float>
Laplace.Samples(values: float [], location: float, scale: float) : unit
Laplace.Samples(rnd: System.Random, location: float, scale: float) : System.Collections.Generic.IEnumerable<float>
Laplace.Samples(rnd: System.Random, values: float [], location: float, scale: float) : unit
val mersenneTwister : unit -> System.Random

Full name: MathNet.Numerics.Random.Random.mersenneTwister
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val ofSeq : source:seq<'T> -> 'T list

Full name: Microsoft.FSharp.Collections.List.ofSeq
val scan : folder:('State -> 'T -> 'State) -> state:'State -> source:seq<'T> -> seq<'State>

Full name: Microsoft.FSharp.Collections.Seq.scan
Normal.Samples(mean: float, stddev: float) : System.Collections.Generic.IEnumerable<float>
Normal.Samples(values: float [], mean: float, stddev: float) : unit
Normal.Samples(rnd: System.Random, mean: float, stddev: float) : System.Collections.Generic.IEnumerable<float>
Normal.Samples(rnd: System.Random, values: float [], mean: float, stddev: float) : unit
Cauchy.Samples(location: float, scale: float) : System.Collections.Generic.IEnumerable<float>
Cauchy.Samples(values: float [], location: float, scale: float) : unit
Cauchy.Samples(rnd: System.Random, location: float, scale: float) : System.Collections.Generic.IEnumerable<float>
Cauchy.Samples(rnd: System.Random, values: float [], location: float, scale: float) : unit
val gammaStats : float * float * float * float * float * float

Full name: Probability.gammaStats
property Gamma.Mean: float
property Gamma.Variance: float
property Gamma.StdDev: float
property Gamma.Entropy: float
property Gamma.Skewness: float
property Gamma.Mode: float
val nd : float

Full name: Probability.nd
Normal.Density(x: float) : float
val ndLn : float

Full name: Probability.ndLn
Normal.DensityLn(x: float) : float
val nc : float

Full name: Probability.nc
Normal.CumulativeDistribution(x: float) : float
val nic : float

Full name: Probability.nic
Normal.InverseCumulativeDistribution(p: float) : float
val nd2 : float

Full name: Probability.nd2
Normal.PDF(mean: float, stddev: float, x: float) : float
val sqrt : value:'T -> 'U (requires member Sqrt)

Full name: Microsoft.FSharp.Core.Operators.sqrt
val ndLn2 : float

Full name: Probability.ndLn2
Normal.PDFLn(mean: float, stddev: float, x: float) : float
val nc2 : float

Full name: Probability.nc2
Normal.CDF(mean: float, stddev: float, x: float) : float
val nic2 : float

Full name: Probability.nic2
Normal.InvCDF(mean: float, stddev: float, p: float) : float
val s1 : rng:System.Random -> float

Full name: Probability.s1


 Transform a sample from a distribution
val rng : System.Random
val tanh : value:'T -> 'T (requires member Tanh)

Full name: Microsoft.FSharp.Core.Operators.tanh
module Sample

from MathNet.Numerics.Distributions
val normal : mean:float -> stddev:float -> rng:System.Random -> float

Full name: MathNet.Numerics.Distributions.Sample.normal
val s1f : rng:System.Random -> float

Full name: Probability.s1f


 But we really want to transform the function, not the resulting sample:
val map : f:('a -> 'T) -> dist:(System.Random -> 'a) -> rng:System.Random -> 'T

Full name: MathNet.Numerics.Distributions.Sample.map
val s1s : rng:System.Random -> seq<float>

Full name: Probability.s1s


 Exactly the same also works with functions generating full sequences
val mapSeq : f:('a -> 'T) -> dist:(System.Random -> #seq<'a>) -> rng:System.Random -> seq<'T>

Full name: MathNet.Numerics.Distributions.Sample.mapSeq
val normalSeq : mean:float -> stddev:float -> rng:System.Random -> System.Collections.Generic.IEnumerable<float>

Full name: MathNet.Numerics.Distributions.Sample.normalSeq
val s2 : rng:System.Random -> float

Full name: Probability.s2


 Now with multiple distributions, e.g. their product:
val cauchy : location:float -> scale:float -> rng:System.Random -> float

Full name: MathNet.Numerics.Distributions.Sample.cauchy
val s2f : rng:System.Random -> float

Full name: Probability.s2f
val map2 : f:('a -> 'b -> 'T) -> dist1:(System.Random -> 'a) -> dist2:(System.Random -> 'b) -> rng:System.Random -> 'T

Full name: MathNet.Numerics.Distributions.Sample.map2
val s2s : rng:System.Random -> seq<float>

Full name: Probability.s2s
val mapSeq2 : f:('a -> 'b -> 'T) -> dist1:(System.Random -> #seq<'a>) -> dist2:(System.Random -> #seq<'b>) -> rng:System.Random -> seq<'T>

Full name: MathNet.Numerics.Distributions.Sample.mapSeq2
val cauchySeq : location:float -> scale:float -> rng:System.Random -> System.Collections.Generic.IEnumerable<float>

Full name: MathNet.Numerics.Distributions.Sample.cauchySeq