[gnumeric] R.PNORM2: New function for the normal distribution's cdf over a range.



commit cee7963f4c66b21b99a34850a61657f8cbc7ad43
Author: Morten Welinder <terra gnome org>
Date:   Sun May 19 16:01:01 2013 -0400

    R.PNORM2: New function for the normal distribution's cdf over a range.
    
    R doesn't actually have this function, but it should.

 ChangeLog                  |    1 +
 NEWS                       |    1 +
 plugins/fn-r/functions.c   |   29 +++++++++++++++++
 plugins/fn-r/generate      |    9 ++++-
 plugins/fn-r/plugin.xml.in |    1 +
 samples/pnorm2.gnumeric    |  Bin 0 -> 2707 bytes
 src/mathfunc.c             |   73 +++++++++++++++++++++++++++----------------
 src/mathfunc.h             |    2 +-
 8 files changed, 86 insertions(+), 30 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index d6554f4..2f4d379 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,7 @@
 
        * src/mathfunc.c (ptukey_wprob): Sanitize handling of integration
        boundaries.
+       (pnorm2): Get rid of mu and sigma arguments.  Improve accuracy.
 
 2013-05-18  Morten Welinder  <terra gnome org>
 
diff --git a/NEWS b/NEWS
index de24be7..d477f22 100644
--- a/NEWS
+++ b/NEWS
@@ -20,6 +20,7 @@ Morten:
        * Improve mathfuns tests.  [#700295]
        * Add new R.PTUKEY function.  [#700132]
        * Fix missing translation of certain function examples.
+       * Add new R.PNORM2 function.
 
 --------------------------------------------------------------------------
 Gnumeric 1.12.2
diff --git a/plugins/fn-r/functions.c b/plugins/fn-r/functions.c
index b25b8c7..3eaf171 100644
--- a/plugins/fn-r/functions.c
+++ b/plugins/fn-r/functions.c
@@ -67,6 +67,28 @@ gnumeric_r_pnorm (GnmFuncEvalInfo *ei, GnmValue const * const *args)
 /* ------------------------------------------------------------------------- */
 
 
+static GnmFuncHelp const help_r_pnorm2[] = {
+       { GNM_FUNC_HELP_NAME, F_("R.PNORM2:cumulative distribution function of the normal distribution") },
+       { GNM_FUNC_HELP_ARG, F_("x1:first observation") },
+       { GNM_FUNC_HELP_ARG, F_("x2:first observation") },
+       { GNM_FUNC_HELP_ARG, F_("log_p:if true, log of the probability is used") },
+       { GNM_FUNC_HELP_DESCRIPTION, F_("This function returns the cumulative distribution function of the 
normal distribution.") },
+       { GNM_FUNC_HELP_END }
+};
+
+static GnmValue *
+gnumeric_r_pnorm2 (GnmFuncEvalInfo *ei, GnmValue const * const *args)
+{
+       gnm_float x1 = value_get_as_float (args[0]);
+       gnm_float x2 = value_get_as_float (args[1]);
+       gboolean log_p = args[2] ? value_get_as_checked_bool (args[2]) : FALSE;
+
+       return value_new_float (pnorm2 (x1, x2, log_p));
+}
+
+/* ------------------------------------------------------------------------- */
+
+
 static GnmFuncHelp const help_r_qnorm[] = {
        { GNM_FUNC_HELP_NAME, F_("R.QNORM:probability quantile function of the normal distribution") },
        { GNM_FUNC_HELP_ARG, F_("p:probability") },
@@ -1398,6 +1420,13 @@ GnmFuncDescriptor const stat_functions[] = {
                GNM_FUNC_SIMPLE, GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC, GNM_FUNC_TEST_STATUS_NO_TESTSUITE,
        },
        {
+               "r.pnorm2",
+               "ff|b",
+               help_r_pnorm2,
+               gnumeric_r_pnorm2, NULL, NULL, NULL,
+               GNM_FUNC_SIMPLE, GNM_FUNC_IMPL_STATUS_UNIQUE_TO_GNUMERIC, GNM_FUNC_TEST_STATUS_NO_TESTSUITE,
+       },
+       {
                "r.qnorm",
                "fff|bb",
                help_r_qnorm,
diff --git a/plugins/fn-r/generate b/plugins/fn-r/generate
index e01a1c8..2ac0e63 100644
--- a/plugins/fn-r/generate
+++ b/plugins/fn-r/generate
@@ -27,11 +27,13 @@ my %defaults;
                  'scale' => "the scale parameter $of",
                  );
 
-    $funcs{'dnorm'} = $funcs{'pnorm'} = $funcs{'qnorm'} =
+    $funcs{'dnorm'} = $funcs{'pnorm'} = $funcs{'pnorm2'} = $funcs{'qnorm'} =
        [\&distribution,
         'normal',
         ({ 'mu' => "mean $of",
            'sigma' => "standard deviation $of",
+           'x1' => 'first observation',
+           'x2' => 'first observation',
            @common })];
 
     $funcs{'dlnorm'} = $funcs{'plnorm'} = $funcs{'qlnorm'} =
@@ -319,7 +321,10 @@ sub distribution {
               (defined ($def) ? " : $def" : "") .
               ";\n");
 
-       if (defined ($def) && $typespec !~ /\|/) {
+       if ($typespec =~ /\|/) {
+           die "$0: argument $name for $func needs a default"
+               unless defined $def;
+       } elsif (defined ($def)) {
            $typespec .= "|" ;
        }
        $typespec .= $type_spec{$type};
diff --git a/plugins/fn-r/plugin.xml.in b/plugins/fn-r/plugin.xml.in
index f3eb79f..acd1b03 100644
--- a/plugins/fn-r/plugin.xml.in
+++ b/plugins/fn-r/plugin.xml.in
@@ -40,6 +40,7 @@
                                <function name="r.plnorm"/>
                                <function name="r.pnbinom"/>
                                <function name="r.pnorm"/>
+                               <function name="r.pnorm2"/>
                                <function name="r.ppois"/>
                                <function name="r.psnorm"/>
                                <function name="r.pst"/>
diff --git a/samples/pnorm2.gnumeric b/samples/pnorm2.gnumeric
new file mode 100644
index 0000000..a13ca07
Binary files /dev/null and b/samples/pnorm2.gnumeric differ
diff --git a/src/mathfunc.c b/src/mathfunc.c
index 77355c5..a93fd85 100644
--- a/src/mathfunc.c
+++ b/src/mathfunc.c
@@ -5287,7 +5287,7 @@ static gnm_float ptukey_wprob(gnm_float w, gnm_float rr, gnm_float cc)
            /* then doesn't contribute to integral */
 
            // rinsum = (pplus * 0.5) - (pminus * 0.5);
-           rinsum = pnorm2 (ac - w, ac, 0.0, 1.0, FALSE);
+           rinsum = pnorm2 (ac - w, ac, FALSE);
            if (rinsum >= gnm_exp(C1 / cc1)) {
                rinsum = (aleg[j-1] * gnm_exp(-(0.5 * qexpo))) * gnm_pow(rinsum, cc1);
                elsum += rinsum;
@@ -5552,47 +5552,66 @@ swap_log_tail (gnm_float lp)
 
 
 gnm_float
-pnorm2 (gnm_float x1, gnm_float x2, gnm_float mu, gnm_float sigma, gboolean log_p)
+pnorm2 (gnm_float x1, gnm_float x2, gboolean log_p)
 {
-       gnm_float raw;
-       const gnm_float RAWOK = 0.1;
-       const gnm_float LOG_RAWOK = GNM_const(-2.3025850929940455);
-
-       if (gnm_isnan(x1) || gnm_isnan(x2) ||
-           gnm_isnan(mu) || gnm_isnan(sigma) ||
-           sigma <= 0)
+       if (gnm_isnan(x1) || gnm_isnan(x2))
                return gnm_nan;
 
        if (x1 > x2)
-               return log_p ? gnm_nan : 0 - pnorm2 (x2, x1, mu, sigma, log_p);
+               return log_p ? gnm_nan : 0 - pnorm2 (x2, x1, log_p);
 
        if (x1 == x2)
                return R_D__0;
 
-       x1 = (x1 - mu) / sigma;
-       x2 = (x2 - mu) / sigma;
+       if (x1 == gnm_ninf)
+               return pnorm (x2, 0.0, 1.0, TRUE, log_p);
+
+       if (x2 == gnm_pinf)
+               return pnorm (x1, 0.0, 1.0, FALSE, log_p);
 
-       /* At this point x1<x2 and we can assume N(0,1).  */
+       if (x1 == 0 && !log_p) {
+               return gnm_erf (x2 / M_SQRT2gnum) / 2;
+
+               /*
+                * for the log case we can use this log(erf(x)) expansion:
+                * (log(2 x)-(log(pi))/2)-x^2/3+(2 x^4)/45-(8 x^6)/2835-(4 x^8)/14175+(32 x^10)/467775+(736 
x^12)/1915538625-(2944 x^14)/1915538625+(5024 x^16)/44405668125+(49690112 x^18)/1754068296605625+O(x^20)
+                */
+       }
+       if (x2 == 0 && !log_p) {
+               return gnm_erf (x1 / -M_SQRT2gnum) / 2;
+       }
 
-       if (x1 < 0) {
-               gnm_float p1 = pnorm (x1, 0.0, 1.0, TRUE, log_p);
-               gnm_float p2 = pnorm (x2, 0.0, 1.0, TRUE, log_p);
-               raw = log_p ? logspace_sub (p2, p1) : p2 - p1;
+       if (x1 <= 0 && x2 >= 0) {
+               /* The interval spans 0.  */
+               gnm_float p1 = pnorm2 (0, MIN (-x1, x2), log_p);
+               gnm_float p2 = pnorm2 (MIN (-x1, x2), MAX (-x1, x2), log_p);
+               return log_p
+                       ? logspace_add (p1 + M_LN2gnum, p2)
+                       : 2 * p1 + p2;
+       } else if (x1 < 0) {
+               /* Both < 0 -- use symmetry */
+               return pnorm2 (-x2, -x1, log_p);
        } else {
+               /* Both >= 0 */
                gnm_float p1C = pnorm (x1, 0.0, 1.0, FALSE, log_p);
                gnm_float p2C = pnorm (x2, 0.0, 1.0, FALSE, log_p);
-               raw = log_p ? logspace_sub (p1C, p2C) : p1C - p2C;
-       }
+               gnm_float raw = log_p ? logspace_sub (p1C, p2C) : p1C - p2C;
+               gnm_float dx, d1, d2, ub, lb;
 
-       if (raw > (log_p ? LOG_RAWOK : RAWOK))
-               return raw;
+               if (gnm_abs (p1C - p2C) * 32 > gnm_abs (p1C + p2C))
+                       return raw;
 
-       if (0)
-               g_printerr ("x1=%.10g x2=%.10g  p1=%.10g p2=%.10g\n",
-                           x1, x2,
-                           pnorm (x1, 0.0, 1.0, TRUE, FALSE),
-                           pnorm (x2, 0.0, 1.0, TRUE, FALSE));
-       return raw;
+               /* dnorm is strictly decreasing in this area.  */
+               dx = log_p ? gnm_log (x2 - x1) : x2 - x1;
+               d1 = dnorm (x1, 0.0, 1.0, log_p);
+               d2 = dnorm (x2, 0.0, 1.0, log_p);
+               ub = log_p ? dx + d1 : dx * d1;  /* upper bound */
+               lb = log_p ? dx + d2 : dx * d2;  /* lower bound */
+
+               raw = MAX (raw, lb);
+               raw = MIN (raw, ub);
+               return raw;
+       }
 }
 
 
diff --git a/src/mathfunc.h b/src/mathfunc.h
index ccd7d72..8de5b47 100644
--- a/src/mathfunc.h
+++ b/src/mathfunc.h
@@ -56,7 +56,7 @@ gnm_float bessel_k (gnm_float x, gnm_float alpha, gnm_float expo);
 /* The normal distribution.  */
 gnm_float dnorm (gnm_float x, gnm_float mu, gnm_float sigma, gboolean give_log);
 gnm_float pnorm (gnm_float x, gnm_float mu, gnm_float sigma, gboolean lower_tail, gboolean log_p);
-gnm_float pnorm2 (gnm_float x1, gnm_float x2, gnm_float mu, gnm_float sigma, gboolean log_p);
+gnm_float pnorm2 (gnm_float x1, gnm_float x2, gboolean log_p);
 gnm_float qnorm (gnm_float p, gnm_float mu, gnm_float sigma, gboolean lower_tail, gboolean log_p);
 
 /* The log-normal distribution.  */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]