diff --git a/src/Numerics/Distributions/LogNormal.cs b/src/Numerics/Distributions/LogNormal.cs index a827ecbd..4dd69055 100644 --- a/src/Numerics/Distributions/LogNormal.cs +++ b/src/Numerics/Distributions/LogNormal.cs @@ -259,6 +259,7 @@ namespace MathNet.Numerics.Distributions /// /// The location at which to compute the density. /// the density at . + /// public double Density(double x) { if (x < 0.0) @@ -275,6 +276,7 @@ namespace MathNet.Numerics.Distributions /// /// The location at which to compute the log density. /// the log density at . + /// public double DensityLn(double x) { if (x < 0.0) @@ -291,11 +293,24 @@ namespace MathNet.Numerics.Distributions /// /// The location at which to compute the cumulative distribution function. /// the cumulative distribution at location . + /// public double CumulativeDistribution(double x) { - return x < 0.0 - ? 0.0 - : 0.5*(1.0 + SpecialFunctions.Erf((Math.Log(x) - _mu)/(_sigma*Constants.Sqrt2))); + return x < 0.0 ? 0.0 + : 0.5*SpecialFunctions.Erfc((_mu - Math.Log(x))/(_sigma*Constants.Sqrt2)); + } + + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the 'quantile function'. + /// + /// The location at which to compute the inverse cumulative density. + /// the inverse cumulative density at . + /// + public double InverseCumulativeDistribution(double p) + { + return p <= 0.0 ? 0.0 : p >= 1.0 ? double.PositiveInfinity + : Math.Exp(_mu - _sigma*Constants.Sqrt2*SpecialFunctions.ErfcInv(2.0*p)); } /// @@ -323,9 +338,10 @@ namespace MathNet.Numerics.Distributions /// The log-scale (μ) of the distribution. /// The shape (σ) of the distribution. Range: σ ≥ 0. /// the density at . + /// public static double PDF(double mu, double sigma, double x) { - if (sigma < 0.0) throw new ArgumentOutOfRangeException(Resources.InvalidDistributionParameters); + if (sigma < 0.0) throw new ArgumentOutOfRangeException("sigma", Resources.InvalidDistributionParameters); if (x < 0.0) { @@ -343,9 +359,10 @@ namespace MathNet.Numerics.Distributions /// The log-scale (μ) of the distribution. /// The shape (σ) of the distribution. Range: σ ≥ 0. /// the log density at . + /// public static double PDFLn(double mu, double sigma, double x) { - if (sigma < 0.0) throw new ArgumentOutOfRangeException(Resources.InvalidDistributionParameters); + if (sigma < 0.0) throw new ArgumentOutOfRangeException("sigma", Resources.InvalidDistributionParameters); if (x < 0.0) { @@ -363,15 +380,32 @@ namespace MathNet.Numerics.Distributions /// The log-scale (μ) of the distribution. /// The shape (σ) of the distribution. Range: σ ≥ 0. /// the cumulative distribution at location . + /// public static double CDF(double mu, double sigma, double x) { - if (sigma < 0.0) throw new ArgumentOutOfRangeException(Resources.InvalidDistributionParameters); + if (sigma < 0.0) throw new ArgumentOutOfRangeException("sigma", Resources.InvalidDistributionParameters); - return x < 0.0 - ? 0.0 + return x < 0.0 ? 0.0 : 0.5*(1.0 + SpecialFunctions.Erf((Math.Log(x) - mu)/(sigma*Constants.Sqrt2))); } + /// + /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution + /// at the given probability. This is also known as the 'quantile function'. + /// + /// The location at which to compute the inverse cumulative density. + /// The log-scale (μ) of the distribution. + /// The shape (σ) of the distribution. Range: σ ≥ 0. + /// the inverse cumulative density at . + /// + public static double InvCDF(double mu, double sigma, double p) + { + if (sigma < 0.0) throw new ArgumentOutOfRangeException("sigma", Resources.InvalidDistributionParameters); + + return p <= 0.0 ? 0.0 : p >= 1.0 ? double.PositiveInfinity + : Math.Exp(mu - sigma*Constants.Sqrt2*SpecialFunctions.ErfcInv(2.0*p)); + } + /// /// Generates a sample from the log-normal distribution using the Box-Muller algorithm. /// diff --git a/src/Numerics/Distributions/Normal.cs b/src/Numerics/Distributions/Normal.cs index 737098b4..f7248882 100644 --- a/src/Numerics/Distributions/Normal.cs +++ b/src/Numerics/Distributions/Normal.cs @@ -279,6 +279,7 @@ namespace MathNet.Numerics.Distributions /// /// The location at which to compute the density. /// the density at . + /// public double Density(double x) { var d = (x - _mean)/_stdDev; @@ -290,6 +291,7 @@ namespace MathNet.Numerics.Distributions /// /// The location at which to compute the log density. /// the log density at . + /// public double DensityLn(double x) { var d = (x - _mean)/_stdDev; @@ -301,17 +303,19 @@ namespace MathNet.Numerics.Distributions /// /// The location at which to compute the cumulative distribution function. /// the cumulative distribution at location . + /// public double CumulativeDistribution(double x) { - return 0.5*(1.0 + SpecialFunctions.Erf((x - _mean)/(_stdDev*Constants.Sqrt2))); + return 0.5*SpecialFunctions.Erfc((_mean - x)/(_stdDev*Constants.Sqrt2)); } /// /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution - /// at the given probability. + /// at the given probability. This is also known as the 'quantile function'. /// /// The location at which to compute the inverse cumulative density. /// the inverse cumulative density at . + /// public double InverseCumulativeDistribution(double p) { return _mean - (_stdDev*Constants.Sqrt2*SpecialFunctions.ErfcInv(2.0*p)); @@ -368,6 +372,7 @@ namespace MathNet.Numerics.Distributions /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. /// The location at which to compute the density. /// the density at . + /// public static double PDF(double mean, double stddev, double x) { if (stddev < 0.0) throw new ArgumentOutOfRangeException("stddev", Resources.InvalidDistributionParameters); @@ -383,6 +388,7 @@ namespace MathNet.Numerics.Distributions /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. /// The location at which to compute the density. /// the log density at . + /// public static double PDFLn(double mean, double stddev, double x) { if (stddev < 0.0) throw new ArgumentOutOfRangeException("stddev", Resources.InvalidDistributionParameters); @@ -398,6 +404,7 @@ namespace MathNet.Numerics.Distributions /// The mean (μ) of the normal distribution. /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. /// the cumulative distribution at location . + /// public static double CDF(double mean, double stddev, double x) { if (stddev < 0.0) throw new ArgumentOutOfRangeException("stddev", Resources.InvalidDistributionParameters); @@ -407,12 +414,13 @@ namespace MathNet.Numerics.Distributions /// /// Computes the inverse of the cumulative distribution function (InvCDF) for the distribution - /// at the given probability. + /// at the given probability. This is also known as the 'quantile function'. /// /// The location at which to compute the inverse cumulative density. /// The mean (μ) of the normal distribution. /// The standard deviation (σ) of the normal distribution. Range: σ ≥ 0. /// the inverse cumulative density at . + /// public static double InvCDF(double mean, double stddev, double p) { if (stddev < 0.0) throw new ArgumentOutOfRangeException("stddev", Resources.InvalidDistributionParameters); diff --git a/src/UnitTests/DistributionTests/Continuous/LogNormalTests.cs b/src/UnitTests/DistributionTests/Continuous/LogNormalTests.cs index 84167ef5..eee3f768 100644 --- a/src/UnitTests/DistributionTests/Continuous/LogNormalTests.cs +++ b/src/UnitTests/DistributionTests/Continuous/LogNormalTests.cs @@ -523,6 +523,40 @@ namespace MathNet.Numerics.UnitTests.DistributionTests.Continuous AssertHelpers.AlmostEqual(f, LogNormal.CDF(mu, sigma, x), 8); } + /// + /// Validate inverse cumulative distribution. + /// + /// Mu parameter. + /// Sigma value. + /// Input X value. + /// Expected value. + [TestCase(-0.100000, 0.100000, 0.500000, 0.0000000015011556178148777579869633555518882664666520593658)] + [TestCase(-0.100000, 0.100000, 0.800000, 0.10908001076375810900224507908874442583171381706127)] + [TestCase(-0.100000, 1.500000, 0.100000, 0.070999149762464508991968731574953594549291668468349)] + [TestCase(-0.100000, 1.500000, 0.500000, 0.34626224992888089297789445771047690175505847991946)] + [TestCase(-0.100000, 1.500000, 0.800000, 0.46728530589487698517090261668589508746353129242404)] + [TestCase(-0.100000, 2.500000, 0.100000, 0.18914969879695093477606645992572208111152994999076)] + [TestCase(-0.100000, 2.500000, 0.500000, 0.40622798321378106125020505907901206714868922279347)] + [TestCase(-0.100000, 2.500000, 0.800000, 0.48035707589956665425068652807400957345208517749893)] + [TestCase(1.500000, 1.500000, 0.100000, 0.005621455876973168709588070988239748831823850202953)] + [TestCase(1.500000, 1.500000, 0.500000, 0.07185716187918271235246980951571040808235628115265)] + [TestCase(1.500000, 1.500000, 0.800000, 0.12532699044614938400496547188720940854423187977236)] + [TestCase(1.500000, 2.500000, 0.100000, 0.064125647996943514411570834861724406903677144126117)] + [TestCase(1.500000, 2.500000, 0.500000, 0.19017302281590810871719754032332631806011441356498)] + [TestCase(1.500000, 2.500000, 0.800000, 0.24533064397555500690927047163085419096928289095201)] + [TestCase(2.500000, 1.500000, 0.100000, 0.00068304052220788502001572635016579586444611070077399)] + [TestCase(2.500000, 1.500000, 0.500000, 0.016636862816580533038130583128179878924863968664206)] + [TestCase(2.500000, 1.500000, 0.800000, 0.034729001282904174941366974418836262996834852343018)] + [TestCase(2.500000, 2.500000, 0.100000, 0.027363708266690978870139978537188410215717307180775)] + [TestCase(2.500000, 2.500000, 0.500000, 0.10075543423327634536450625420610429181921642201567)] + [TestCase(2.500000, 2.500000, 0.800000, 0.13802019192453118732001307556787218421918336849121)] + public void ValidateInverseCumulativeDistribution(double mu, double sigma, double x, double f) + { + var n = new LogNormal(mu, sigma); + AssertHelpers.AlmostEqual(x, n.InverseCumulativeDistribution(f), 8); + AssertHelpers.AlmostEqual(x, LogNormal.InvCDF(mu, sigma, f), 8); + } + /// /// Can estimate distribution parameters. ///