有没有一个C#库可以执行Excel的NORMINV函数?

13

我正在运行一些蒙特卡罗模拟,并广泛使用Office Interrop中的Excel函数NORM.INV。这个函数需要三个参数(概率、平均值、标准偏差),并返回累积分布的反函数。

我想把我的代码移植到Web应用程序中,但这将需要在服务器上安装Excel。有人知道是否有C#统计库具有与NORM.INV等效的函数吗?


1
无论你做什么,都不要在你的 Web 服务器上安装 Excel。如果必须要使用,请自己编写函数。 - MusiGenesis
@MusiGenesis:为什么你对在Web服务器上使用Excel如此敌视?我同意这不是理想的,但它似乎比尝试编写自己的统计函数要好,对吧? - Portman
1
我并不反对在Web服务器上使用Excel - 我非常害怕它。Office对象是非常庞大的东西,你不希望你的Web应用程序因为像分布函数这样小的事情而承受它们的实例化带来的影响。确实,你不想重复造轮子,但同样也不想用榴弹炮杀死老鼠。编写自己的函数肯定更好。 - MusiGenesis
@MusiGenesis:恕我不同意。这是一个蒙特卡罗模拟,该函数被调用了25,000次。Excel工作簿对象大小为3MB。目前我可以在大约10秒内调用该函数25,000次。我第一次尝试使用@Peter提到的算法编写它时没有错误(耶!),但使用了40MB并且运行时间为20分钟(噢!)。 - Portman
1
你将面临的问题是在Web应用程序中使用Excel时,当大量用户同时发出请求时。每个请求都需要创建自己的Excel工作簿对象,或者您必须在请求之间共享一个工作簿对象 - 无论哪种方式都会引起问题。如果您决定使用Excel,请确保使用多个并发用户对您的应用程序进行重负载测试。 - MusiGenesis
7个回答

10

Meta.Numerics有你所需要的东西。这是使用该库完成此操作的代码:

Distribution n = new NormalDistribution(mean, standardDeviation);
double x = n.InverseLeftProbability(probability);

如果您想生成正态分布随机数,那么使用GetRandomValue函数会更快。


5

https://numerics.mathdotnet.com/有一个相当不错的库,处理统计学(所以我假设是CDF)。我没有使用过它,所以无法确定它是否确实是你想要的,但看起来应该是。


1
CDF和反函数CDF目前不是该项目的一部分。至少在我查看项目时没有找到它们。 - user1345223
现在似乎已经有了(6年后 :)):double InvCDF(double mean, double stddev, double p) https://numerics.mathdotnet.com/api/MathNet.Numerics.Distributions/Normal.htm - grasmi

5

我还用F#进行了实现,你可以在这里查看博客文章http://weblogs.asp.net/esanchez/archive/2010/08/08/a-quick-and-dirty-implementation-of-excel-norminv-in-f.aspx - Édgar Sánchez Gordón
1
我将这个与Euan Dean的实现以及Meta.Numerics中的一个参考实现(Excel)进行了比较。这个是最精确的,而Euan Dean的是最快的。Meta.Numerics非常精确但非常慢。 - Andriy Volkov

4

包括系数的反正态分布函数可以在这里找到。相对误差的绝对值小于1.15×10−9。

public static class NormalDistributionConfidenceCalculator
{
    /// <summary>
    /// 
    /// </summary>
    public static double InverseNormalDistribution(double probability, double min, double max)
    {
        double x = 0;
        double a = 0;
        double b = 1;

        double precision = Math.Pow(10, -3);

        while ((b - a) > precision)
        {
            x = (a + b) / 2;
            if (NormInv(x) > probability)
            {
                b = x;
            }
            else
            {
                a = x;
            }
        }

        if ((max > 0) && (min > 0))
        {
            x = x * (max - min) + min;
        }
        return x;
    }

    /// <summary>
    /// Returns the cumulative density function evaluated at A given value.
    /// </summary>
    /// <param name="x">A position on the x-axis.</param>
    /// <param name="mean"></param>
    /// <param name="sigma"></param>
    /// <returns>The cumulative density function evaluated at <C>x</C>.</returns>
    /// <remarks>The value of the cumulative density function at A point <C>x</C> is
    /// probability that the value of A random variable having this normal density is
    /// less than or equal to <C>x</C>.
    /// </remarks>
    public static double NormalDistribution(double x, double mean, double sigma)
    {
        // This algorithm is ported from dcdflib:
        // Cody, W.D. (1993). "ALGORITHM 715: SPECFUN - A Portabel FORTRAN
        // Package of Special Function Routines and Test Drivers"
        // acm Transactions on Mathematical Software. 19, 22-32.
        int i;
        double del, xden, xnum, xsq;
        double result, ccum;
        double arg = (x - mean) / sigma;
        const double sixten = 1.60e0;
        const double sqrpi = 3.9894228040143267794e-1;
        const double thrsh = 0.66291e0;
        const double root32 = 5.656854248e0;
        const double zero = 0.0e0;
        const double min = Double.Epsilon;
        double z = arg;
        double y = Math.Abs(z);
        const double half = 0.5e0;
        const double one = 1.0e0;

        double[] a =
            {
                2.2352520354606839287e00, 1.6102823106855587881e02, 1.0676894854603709582e03,
                1.8154981253343561249e04, 6.5682337918207449113e-2
            };

        double[] b =
            {
                4.7202581904688241870e01, 9.7609855173777669322e02, 1.0260932208618978205e04,
                4.5507789335026729956e04
            };

        double[] c =
            {
                3.9894151208813466764e-1, 8.8831497943883759412e00, 9.3506656132177855979e01,
                5.9727027639480026226e02, 2.4945375852903726711e03, 6.8481904505362823326e03,
                1.1602651437647350124e04, 9.8427148383839780218e03, 1.0765576773720192317e-8
            };

        double[] d =
            {
                2.2266688044328115691e01, 2.3538790178262499861e02, 1.5193775994075548050e03,
                6.4855582982667607550e03, 1.8615571640885098091e04, 3.4900952721145977266e04,
                3.8912003286093271411e04, 1.9685429676859990727e04
            };
        double[] p =
            {
                2.1589853405795699e-1, 1.274011611602473639e-1, 2.2235277870649807e-2,
                1.421619193227893466e-3, 2.9112874951168792e-5, 2.307344176494017303e-2
            };


        double[] q =
            {
                1.28426009614491121e00, 4.68238212480865118e-1, 6.59881378689285515e-2,
                3.78239633202758244e-3, 7.29751555083966205e-5
            };
        if (y <= thrsh)
        {
            //
            // Evaluate  anorm  for  |X| <= 0.66291
            //
            xsq = zero;
            if (y > double.Epsilon) xsq = z * z;
            xnum = a[4] * xsq;
            xden = xsq;
            for (i = 0; i < 3; i++)
            {
                xnum = (xnum + a[i]) * xsq;
                xden = (xden + b[i]) * xsq;
            }
            result = z * (xnum + a[3]) / (xden + b[3]);
            double temp = result;
            result = half + temp;
        }

            //
        // Evaluate  anorm  for 0.66291 <= |X| <= sqrt(32)
        //
        else if (y <= root32)
        {
            xnum = c[8] * y;
            xden = y;
            for (i = 0; i < 7; i++)
            {
                xnum = (xnum + c[i]) * y;
                xden = (xden + d[i]) * y;
            }
            result = (xnum + c[7]) / (xden + d[7]);
            xsq = Math.Floor(y * sixten) / sixten;
            del = (y - xsq) * (y + xsq);
            result = Math.Exp(-(xsq * xsq * half)) * Math.Exp(-(del * half)) * result;
            ccum = one - result;
            if (z > zero)
            {
                result = ccum;
            }
        }

            //
        // Evaluate  anorm  for |X| > sqrt(32)
        //
        else
        {
            xsq = one / (z * z);
            xnum = p[5] * xsq;
            xden = xsq;
            for (i = 0; i < 4; i++)
            {
                xnum = (xnum + p[i]) * xsq;
                xden = (xden + q[i]) * xsq;
            }
            result = xsq * (xnum + p[4]) / (xden + q[4]);
            result = (sqrpi - result) / y;
            xsq = Math.Floor(z * sixten) / sixten;
            del = (z - xsq) * (z + xsq);
            result = Math.Exp(-(xsq * xsq * half)) * Math.Exp(-(del * half)) * result;
            ccum = one - result;
            if (z > zero)
            {
                result = ccum;
            }
        }

        if (result < min)
            result = 0.0e0;
        return result;
    }

    /// <summary>
    /// Given a probability, a mean, and a standard deviation, an x value can be calculated.
    /// </summary>
    /// <returns></returns>
    public static double NormInv(double probability)
    {
        const double a1 = -39.6968302866538;
        const double a2 = 220.946098424521;
        const double a3 = -275.928510446969;
        const double a4 = 138.357751867269;
        const double a5 = -30.6647980661472;
        const double a6 = 2.50662827745924;

        const double b1 = -54.4760987982241;
        const double b2 = 161.585836858041;
        const double b3 = -155.698979859887;
        const double b4 = 66.8013118877197;
        const double b5 = -13.2806815528857;

        const double c1 = -7.78489400243029E-03;
        const double c2 = -0.322396458041136;
        const double c3 = -2.40075827716184;
        const double c4 = -2.54973253934373;
        const double c5 = 4.37466414146497;
        const double c6 = 2.93816398269878;

        const double d1 = 7.78469570904146E-03;
        const double d2 = 0.32246712907004;
        const double d3 = 2.445134137143;
        const double d4 = 3.75440866190742;

        //Define break-points
        // using Epsilon is wrong; see link above for reference to 0.02425 value
        //const double pLow = double.Epsilon;
        const double pLow = 0.02425;

        const double pHigh = 1 - pLow;

        //Define work variables
        double q;
        double result = 0;

        // if argument out of bounds.
        // set it to a value within desired precision.
        if (probability <= 0) 
            probability = pLow;

        if (probability >= 1)
            probability = pHigh;

        if (probability < pLow)
        {
            //Rational approximation for lower region
            q = Math.Sqrt(-2 * Math.Log(probability));
            result = (((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
        }
        else if (probability <= pHigh)
        {
            //Rational approximation for lower region
            q = probability - 0.5;
            double r = q * q;
            result = (((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q /
                     (((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1);
        }
        else if (probability < 1)
        {
            //Rational approximation for upper region
            q = Math.Sqrt(-2 * Math.Log(1 - probability));
            result = -(((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) / ((((d1 * q + d2) * q + d3) * q + d4) * q + 1);
        }

        return result;
    }

    /// <summary>
    /// 
    /// </summary>
    /// <param name="probability"></param>
    /// <param name="mean"></param>
    /// <param name="sigma"></param>
    /// <returns></returns>
    public static double NormInv(double probability, double mean, double sigma)
    {
        double x = NormInv(probability);
        return sigma * x + mean;
    }
}

这是从哪里来的?你在哪里使用它了?一些背景信息很重要。 - Portman
请添加上下文和参考资料把以下关于编程的内容从英语翻译成中文。只返回翻译后的文本。 - BuZz

1

我不知道有没有现成的库,但是我找到了这个链接 - http://home.online.no/~pjacklam/notes/invnorm/ - 描述了一个算法。它在许多语言中都有实现,但不包括C#。你可以使用VB.NET版本,或者自己进行移植。


1
也许你可以尝试使用这个组件http://www.smartxls.com,它具有与Excel兼容的运行时计算引擎,并且不需要安装Excel。

0
添加一个图表控件
double result = Chart1.DataManipulator.Statistics.InverseNormalDistribution(probability);
例如概率:0.9,0.4

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接