【问题标题】:How do I generate normal cumulative distribution in Java? its inverse cdf? How about lognormal?如何在 Java 中生成正态累积分布?它的逆cdf?对数正态呢?
【发布时间】:2012-03-03 19:33:54
【问题描述】:

我是 Java 的新手,第二天!我想生成具有正态分布的样本。我正在使用逆变换。

基本上,我想找到逆正态累积分布,然后找到它的逆。并生成样本。

我的问题是:反正态 cdf 是否有内置函数?还是我必须手动编码?

我看到人们在 apache commons 上提到 this。这是内置的吗?还是我必须下载?

如果我必须自己做,你能给我一些建议吗?如果我下载,我的教授是否也必须安装“包”或特殊文件?

提前致谢!

编辑:刚发现我不能使用库,还听说有更简单的方法使用弧度转换正常。

【问题讨论】:

  • 根据经验,java.*javax.* 以外的包不包含在 Java 运行时中。
  • 知道了,我们需要编写代码来模拟正态分布的样本,均值=10,方差=2。那么,我猜任何下载或软件包都会很糟糕?
  • @AdelBoutros:下面的答案很好,但是对普通 cdf 使用逆变换的正确答案是首先从 uniform(0,1) 中提取,然后使用 box-muller 变换公式,即正弦和余弦的函数。无需打包或编码。

标签: java statistics probability


【解决方案1】:

我从未尝试过,但算法团队的人正在使用 Colt,他们对结果很满意。

【讨论】:

  • 据我所知,Colt 没有提供逆累积分布函数。
  • 感谢您的链接。这很有趣。但是 1)OP 知道 dist,所以为什么要使用近似值 2)据我所知,使用插值来估计逆 cdf 似乎并不像看起来那么简单(合理的精度,合理的计算时间)。我只问了一个关于这个here的问题。请考虑使用 colt 添加答案,我会很高兴地道歉、赞成并接受(这里没有讽刺意味!):)
【解决方案2】:

正如上面提到的here

Apache Commons - Math 有你要找的东西。

更具体地说,请查看 NormalDistrubitionImpl 类。

如果你向教授提供所有需要的库,他就不需要下载东西。

更新:

如果你想手动编码(我不知道实际的公式),你可以查看以下链接: http://home.online.no/~pjacklam/notes/invnorm/

有2个人用java实现了它:http://home.online.no/~pjacklam/notes/invnorm/#Java

【讨论】:

  • 所以,我还需要通过电子邮件将我的图书馆发给他吗?是否可以只为我的硬件提交一个文件?
  • 是的,如果您构建 JAR 文件并设置清单。您可以包含引用的库。当然,除非他真的希望你自己编写方法作为分配的一部分
  • @fishtoprecord,谢谢。我想 Apache Commons 更容易?我在我的 Macbook 上导入它时遇到问题,tar.gz 文件一直在引用库下。有什么帮助吗?
  • @fishtoprecords 我发现我无法使用内置库。 :( 所以手工编码?如何在逆正规公式中得到 erf 函数?
  • @AdelBoutros,我听说有一个什么时候可以将正态分布更改为弧度,这样计算起来会更容易吗?我还没弄清楚,但那是“提示”。
【解决方案3】:

我遇到了同样的问题并找到了解决方案,下面的代码会像excel一样给出累积分布函数的结果:

 private static double erf(double x)
{
    //A&S formula 7.1.26
    double a1 = 0.254829592;
    double a2 = -0.284496736;
    double a3 = 1.421413741;
    double a4 = -1.453152027;
    double a5 = 1.061405429;
    double p = 0.3275911;
    x = Math.abs(x);
    double t = 1 / (1 + p * x);
    //Direct calculation using formula 7.1.26 is absolutely correct
    //But calculation of nth order polynomial takes O(n^2) operations
    //return 1 - (a1 * t + a2 * t * t + a3 * t * t * t + a4 * t * t * t * t + a5 * t * t * t * t * t) * Math.Exp(-1 * x * x);

    //Horner's method, takes O(n) operations for nth order polynomial
    return 1 - ((((((a5 * t + a4) * t) + a3) * t + a2) * t) + a1) * t * Math.exp(-1 * x * x);
}
public static double NORMSDIST(double z)
{
    double sign = 1;
    if (z < 0) sign = -1;

    double result=0.5 * (1.0 + sign * erf(Math.abs(z)/Math.sqrt(2)));
    return result;
}

【讨论】:

    【解决方案4】:

    从数学上讲,这是一个难题,您可以考虑一些解决方案。

    Dislcaimer:前面的数学术语。

    您可能已经知道,normalcdf 函数用于计算正态随机变量的概率。因为正态分布是连续的,所以相应的概率密度函数 (normalpdf) 本身并不给出概率,(与 二项式几何 等离散分布相反em> 分布)。相反,曲线下的区域给出了正态随机变量落在 范围值内的概率。因此,您寻找的 normalcdf 函数是 normalpdf 函数的一部分下的区域。

    在数学上,求连续曲线下的面积是微积分的一个基本问题。此类问题的解决方案称为 integralintegrating 一个函数在一个数字范围内意味着找到曲线下的面积和该范围内的最低值之间的面积最高的。

    在大多数情况下,我们可以只集成 pdf 函数来获得 cdf 函数,然后在我们想要的任何地方对其进行评估。问题的核心,以及 Java 中的算法不像人们想象的那么简单的原因是 normalpdf 函数没有 闭式 积分——它是值不能在任何有限的步骤中计算。因此,normalcdf 函数的值特别难以捉摸。

    这个问题有两种主要的解决方案。

    1.数值积分技术

    数值积分技术通过几何近似曲线下面积来解决问题。该区域被分成等宽或不同宽度的矩形或其他形状,每个形状的高度由 pdf 函数给出。矩形的面积之和是曲线下面积的近似值,即对应的概率。这些技术可用于计算任意精度的值,但比第 2 类计算成本更高。使用更好的近似值(例如辛普森规则)可以改进计算。下面是一个简单的数值积分方法。

    public static double normCDF(double z)
    {   double LeftEndpoint = -100;
    int nRectangles = 100000;
        double runningSum = 0;
        double x;
        for(int n = 0; n < nRectangles; n++){
        x = LeftEndpoint + n*(z-LeftEndpoint)/nRectangles;
            runningSum += Math.pow(Math.sqrt(2*Math.PI),-1)*Math.exp(-Math.pow(x,2)/2)*(z-LeftEndpoint)/nRectangles;
        }
        System.out.println(runningSum);
        return runningSum;
    }
    

    2。分析技术

    分析技术利用了这样一个事实,即虽然 normalpdf 没有封闭形式的积分,但 pdf 可以“转换”为称为 泰勒级数,然后逐项积分。基本上,它将 pdf 转换为无限多个简单函数的总和,然后对每个函数进行解析积分,然后将所有积分相加。由于这是一个解析过程,程序员只需在计算系数后将积分级数包含在程序中即可。结果的精度仅取决于计算中包含的总和项的数量,并且往往比数值积分技术更快地接近准确值。例如,Mohammad Alderawy 的解决方案只计算了五个系数。下面是一种包括系数计算的方法,因此您可以计算任意精度的值(实际上,normalcdf 系列不是直接计算的。相反,相关 error 的系数函数 被计算然后通过线性变换进行转换)。然而,由于系数的计算涉及阶乘函数,因此对于大量系数会遇到内存问题。值得庆幸的是,这种方法在前一类解决方案中的方法所需的迭代的一小部分中返回具有更高精度的值,以产生类似的结果。

    public static double normalCDF(double x){
        System.out.println(0.5*(1+erf(x/Math.sqrt(2))));
        return 0.5*(1+erf(x/Math.sqrt(2)));
    }
    
    public static double erf(double z)
    {
        int nTerms = 315;
        double runningSum = 0;
        for(int n = 0; n < nTerms; n++){
            runningSum += Math.pow(-1,n)*Math.pow(z,2*n+1)/(factorial(n)*(2*n+1));
        }
        return (2/Math.sqrt(Math.PI))*runningSum;
    }
    
    static double factorial(int n){
        if(n == 0) return 1;
        if(n == 1) return 1; 
        return n*factorial(n-1);
    }
    

    其他功能

    对于逆函数,由于我们在normalCDF方法中使用了误差函数,所以我们可以类似的方式使用逆误差函数。同样,我们通过解析获得逆误差函数的系数,然后在方法中根据需要计算它们。

    public static double invErf(double z)
    {
        int nTerms = 315;
        double runningSum = 0;
        double[] a = new double[nTerms + 1];
        double[] c = new double[nTerms + 1];
        c[0]=1;
        for(int n = 1; n < nTerms; n++){
            double runningSum2=0;
            for (int k = 0; k <= n-1; k++){
                runningSum2 += c[k]*c[n-1-k]/((k+1)*(2*k+1));
            }
            c[n] = runningSum2;
            runningSum2 = 0;
        }
        for(int n = 0; n < nTerms; n++){
            a[n] = c[n]/(2*n+1);
            runningSum += a[n]*Math.pow((0.5)*Math.sqrt(Math.PI)*z,2*n+1);
        }
        return runningSum;
    }
    
    public static double invNorm(double A){
        return (2/Math.sqrt(2))*invErf(2*A-1);
    }
    

    我没有对数正态函数的方法,但您可以使用相同的想法获得一个。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-07-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-23
      • 2017-08-19
      • 2013-01-28
      • 2020-03-19
      相关资源
      最近更新 更多