This is an automated email from the ASF dual-hosted git repository.

aherbert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-statistics.git


The following commit(s) were added to refs/heads/master by this push:
     new 481e922  STATISTICS-25: Specialise t-distribution for infinite degrees 
freedom
481e922 is described below

commit 481e922f44e72c4b5c4d4d991326077ad87a51d7
Author: Alex Herbert <[email protected]>
AuthorDate: Fri Oct 22 20:02:53 2021 +0100

    STATISTICS-25: Specialise t-distribution for infinite degrees freedom
    
    If the variance of the distribution matches the standard normal
    distribution then delegate to a standard normal distribution.
---
 .../statistics/distribution/TDistribution.java     | 327 +++++++++++++--------
 .../statistics/distribution/test.t.3.properties    |  35 +++
 .../statistics/distribution/test.t.4.properties    |  36 +++
 3 files changed, 279 insertions(+), 119 deletions(-)

diff --git 
a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TDistribution.java
 
b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TDistribution.java
index bda9b40..11dce96 100644
--- 
a/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TDistribution.java
+++ 
b/commons-statistics-distribution/src/main/java/org/apache/commons/statistics/distribution/TDistribution.java
@@ -17,51 +17,221 @@
 package org.apache.commons.statistics.distribution;
 
 import org.apache.commons.numbers.gamma.RegularizedBeta;
-import org.apache.commons.numbers.gamma.Erf;
+import org.apache.commons.rng.UniformRandomProvider;
 import org.apache.commons.numbers.gamma.LogGamma;
 
 /**
  * Implementation of <a 
href='http://en.wikipedia.org/wiki/Student&apos;s_t-distribution'>Student's 
t-distribution</a>.
  */
-public final class TDistribution extends AbstractContinuousDistribution {
-    /** 2. */
-    private static final double TWO = 2;
-    /** 1 / sqrt(2). */
-    private static final double ONE_OVER_SQRT_TWO = 1 / Math.sqrt(2);
-    /** Number of degrees of freedom above which to use the normal 
distribution. */
-    private static final double DOF_THRESHOLD_NORMAL = 2.99e6;
+public abstract class TDistribution extends AbstractContinuousDistribution {
+    /** A standard normal distribution used for calculations.
+     * This is immutable and thread-safe and can be used across instances. */
+    static final NormalDistribution STANDARD_NORMAL = NormalDistribution.of(0, 
1);
 
     /** The degrees of freedom. */
     private final double degreesOfFreedom;
-    /** degreesOfFreedom / 2. */
-    private final double dofOver2;
-    /** Cached value. */
-    private final double factor;
-    /** Cached value. */
-    private final double mean;
-    /** Cached value. */
-    private final double variance;
+
+    /**
+     * Specialisation of the T-distribution used when there are infinite 
degrees of freedom.
+     * In this case the distribution matches a normal distribution. This is 
used when the
+     * variance is not different from 1.0.
+     *
+     * <p>This delegates all methods to the standard normal distribution. 
Instances are
+     * allowed to provide access to the degrees of freedom used during 
construction.
+     */
+    private static class NormalTDistribution extends TDistribution {
+        /**
+         * @param degreesOfFreedom Degrees of freedom.
+         */
+        NormalTDistribution(double degreesOfFreedom) {
+            super(degreesOfFreedom);
+        }
+
+        @Override
+        public double density(double x) {
+            return STANDARD_NORMAL.density(x);
+        }
+
+        @Override
+        public double probability(double x0, double x1) {
+            return STANDARD_NORMAL.probability(x0, x1);
+        }
+
+        @Override
+        public double logDensity(double x) {
+            return STANDARD_NORMAL.logDensity(x);
+        }
+
+        @Override
+        public double cumulativeProbability(double x) {
+            return STANDARD_NORMAL.cumulativeProbability(x);
+        }
+
+        @Override
+        public double inverseCumulativeProbability(double p) {
+            return STANDARD_NORMAL.inverseCumulativeProbability(p);
+        }
+
+        @Override
+        public double getMean() {
+            return 0;
+        }
+
+        @Override
+        public double getVariance() {
+            return 1.0;
+        }
+
+        @Override
+        public Sampler createSampler(UniformRandomProvider rng) {
+            return STANDARD_NORMAL.createSampler(rng);
+        }
+    }
+
+    /**
+     * Implementation of Student's T-distribution.
+     */
+    private static class StudentsTDistribution extends TDistribution {
+        /** 2. */
+        private static final double TWO = 2;
+        /** Number of degrees of freedom above which to use the normal 
distribution.
+         * This is used to check the CDF when the degrees of freedom is large. 
*/
+        private static final double DOF_THRESHOLD_NORMAL = 2.99e6;
+
+        /** degreesOfFreedom / 2. */
+        private final double dofOver2;
+        /** Cached value. */
+        private final double factor;
+        /** Cached value. */
+        private final double mean;
+        /** Cached value. */
+        private final double variance;
+
+        /**
+         * @param degreesOfFreedom Degrees of freedom.
+         * @param variance Precomputed variance
+         */
+        StudentsTDistribution(double degreesOfFreedom, double variance) {
+            super(degreesOfFreedom);
+
+            dofOver2 = 0.5 * degreesOfFreedom;
+            factor = LogGamma.value(dofOver2 + 0.5) -
+                     0.5 * (Math.log(Math.PI) + Math.log(degreesOfFreedom)) -
+                     LogGamma.value(dofOver2);
+            this.variance = variance;
+            mean = degreesOfFreedom > 1 ? 0 : Double.NaN;
+        }
+
+        /**
+         * @param degreesOfFreedom Degrees of freedom.
+         * @return the variance
+         */
+        static double computeVariance(double degreesOfFreedom) {
+            if (degreesOfFreedom == Double.POSITIVE_INFINITY) {
+                return 1;
+            } else if (degreesOfFreedom > TWO) {
+                return degreesOfFreedom / (degreesOfFreedom - 2);
+            } else if (degreesOfFreedom > 1) {
+                return Double.POSITIVE_INFINITY;
+            } else {
+                return Double.NaN;
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public double density(double x) {
+            return Math.exp(logDensity(x));
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public double logDensity(double x) {
+            final double nPlus1Over2 = dofOver2 + 0.5;
+            return factor - nPlus1Over2 * Math.log1p(x * x / 
getDegreesOfFreedom());
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public double cumulativeProbability(double x) {
+            if (x == 0) {
+                return 0.5;
+            }
+            final double df = getDegreesOfFreedom();
+            if (df > DOF_THRESHOLD_NORMAL) {
+                return STANDARD_NORMAL.cumulativeProbability(x);
+            }
+            final double x2 = x * x;
+            // z = 1 / (1 + x^2/df)
+            // Simplify by multiplication by df
+            final double z = df / (df + x2);
+
+            // The RegularizedBeta has the complement:
+            //   I(z, a, b) = 1 - I(1 - z, a, b)
+            // This is used when z > (a + 1) / (2 + b + a).
+            // Detect this condition and directly use the complement.
+            if (z > (dofOver2 + 1) / (2.5 + dofOver2)) {
+                // zc = 1 - z; pc = 1 - p
+                final double zc = x2 / (df + x2);
+                final double pc = RegularizedBeta.value(zc, 0.5, dofOver2);
+
+                return x < 0 ?
+                    // 0.5 * p == 0.5 * (1 - pc) = 0.5 - 0.5 * pc
+                    0.5 - 0.5 * pc :
+                    // 1 - 0.5 * p == 1 - 0.5 * (1 - pc) = 0.5 + 0.5 * pc
+                    0.5 + 0.5 * pc;
+            }
+
+            final double p = RegularizedBeta.value(z, dofOver2, 0.5);
+
+            return x < 0 ?
+                0.5 * p :
+                1 - 0.5 * p;
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * <p>For degrees of freedom parameter {@code df}, the mean is
+         * <ul>
+         *  <li>zero if {@code df > 1}, and</li>
+         *  <li>undefined ({@code Double.NaN}) otherwise.</li>
+         * </ul>
+         */
+        @Override
+        public double getMean() {
+            return mean;
+        }
+
+        /**
+         * {@inheritDoc}
+         *
+         * <p>For degrees of freedom parameter {@code df}, the variance is
+         * <ul>
+         *  <li>{@code df / (df - 2)} if {@code df > 2},</li>
+         *  <li>infinite ({@code Double.POSITIVE_INFINITY}) if {@code 1 < df 
<= 2}, and</li>
+         *  <li>undefined ({@code Double.NaN}) otherwise.</li>
+         * </ul>
+         */
+        @Override
+        public double getVariance() {
+            return variance;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        protected double getMedian() {
+            // Overridden for the probability(double, double) method.
+            // This is intentionally not a public method.
+            return 0;
+        }
+    }
 
     /**
      * @param degreesOfFreedom Degrees of freedom.
      */
     private TDistribution(double degreesOfFreedom) {
         this.degreesOfFreedom = degreesOfFreedom;
-
-        dofOver2 = 0.5 * degreesOfFreedom;
-        factor = LogGamma.value(dofOver2 + 0.5) -
-                 0.5 * (Math.log(Math.PI) + Math.log(degreesOfFreedom)) -
-                 LogGamma.value(dofOver2);
-        if (degreesOfFreedom > TWO) {
-            mean = 0;
-            variance = degreesOfFreedom / (degreesOfFreedom - 2);
-        } else if (degreesOfFreedom > 1) {
-            mean = 0;
-            variance = Double.POSITIVE_INFINITY;
-        } else {
-            mean = Double.NaN;
-            variance = Double.NaN;
-        }
     }
 
     /**
@@ -76,7 +246,13 @@ public final class TDistribution extends 
AbstractContinuousDistribution {
             throw new 
DistributionException(DistributionException.NOT_STRICTLY_POSITIVE,
                                             degreesOfFreedom);
         }
-        return new TDistribution(degreesOfFreedom);
+        // If the variance converges to 1 use a NormalDistribution.
+        // Occurs at 2^55 = 3.60e16
+        final double var = 
StudentsTDistribution.computeVariance(degreesOfFreedom);
+        if (var == 1) {
+            return new NormalTDistribution(degreesOfFreedom);
+        }
+        return new StudentsTDistribution(degreesOfFreedom, var);
     }
 
     /**
@@ -90,56 +266,6 @@ public final class TDistribution extends 
AbstractContinuousDistribution {
 
     /** {@inheritDoc} */
     @Override
-    public double density(double x) {
-        return Math.exp(logDensity(x));
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public double logDensity(double x) {
-        final double nPlus1Over2 = dofOver2 + 0.5;
-        return factor - nPlus1Over2 * Math.log1p(x * x / degreesOfFreedom);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public double cumulativeProbability(double x) {
-        if (x == 0) {
-            return 0.5;
-        }
-        if (degreesOfFreedom > DOF_THRESHOLD_NORMAL) {
-            return 0.5 * (1 + Erf.value(x * ONE_OVER_SQRT_TWO));
-        }
-        final double x2 = x * x;
-        // z = 1 / (1 + x^2/df)
-        // Simplify by multiplication by df
-        final double z = degreesOfFreedom / (degreesOfFreedom + x2);
-
-        // The RegularizedBeta has the complement:
-        //   I(z, a, b) = 1 - I(1 - z, a, b)
-        // This is used when z > (a + 1) / (2 + b + a).
-        // Detect this condition and directly use the complement.
-        if (z > (dofOver2 + 1) / (2.5 + dofOver2)) {
-            // zc = 1 - z; pc = 1 - p
-            final double zc = x2 / (degreesOfFreedom + x2);
-            final double pc = RegularizedBeta.value(zc, 0.5, dofOver2);
-
-            return x < 0 ?
-                // 0.5 * p == 0.5 * (1 - pc) = 0.5 - 0.5 * pc
-                0.5 - 0.5 * pc :
-                // 1 - 0.5 * p == 1 - 0.5 * (1 - pc) = 0.5 + 0.5 * pc
-                0.5 + 0.5 * pc;
-        }
-
-        final double p = RegularizedBeta.value(z, dofOver2, 0.5);
-
-        return x < 0 ?
-            0.5 * p :
-            1 - 0.5 * p;
-    }
-
-    /** {@inheritDoc} */
-    @Override
     public double survivalProbability(double x) {
         // Exploit symmetry
         return cumulativeProbability(-x);
@@ -148,35 +274,6 @@ public final class TDistribution extends 
AbstractContinuousDistribution {
     /**
      * {@inheritDoc}
      *
-     * <p>For degrees of freedom parameter {@code df}, the mean is
-     * <ul>
-     *  <li>zero if {@code df > 1}, and</li>
-     *  <li>undefined ({@code Double.NaN}) otherwise.</li>
-     * </ul>
-     */
-    @Override
-    public double getMean() {
-        return mean;
-    }
-
-    /**
-     * {@inheritDoc}
-     *
-     * <p>For degrees of freedom parameter {@code df}, the variance is
-     * <ul>
-     *  <li>{@code df / (df - 2)} if {@code df > 2},</li>
-     *  <li>infinite ({@code Double.POSITIVE_INFINITY}) if {@code 1 < df <= 
2}, and</li>
-     *  <li>undefined ({@code Double.NaN}) otherwise.</li>
-     * </ul>
-     */
-    @Override
-    public double getVariance() {
-        return variance;
-    }
-
-    /**
-     * {@inheritDoc}
-     *
      * <p>The lower bound of the support is always negative infinity..
      *
      * @return lower bound of the support (always
@@ -200,14 +297,6 @@ public final class TDistribution extends 
AbstractContinuousDistribution {
         return Double.POSITIVE_INFINITY;
     }
 
-    /** {@inheritDoc} */
-    @Override
-    protected double getMedian() {
-        // Overridden for the probability(double, double) method.
-        // This is intentionally not a public method.
-        return 0;
-    }
-
     /**
      * {@inheritDoc}
      *
diff --git 
a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.t.3.properties
 
b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.t.3.properties
new file mode 100644
index 0000000..e40171e
--- /dev/null
+++ 
b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.t.3.properties
@@ -0,0 +1,35 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+parameters = 2.0
+# Limited by cdf inverse mapping
+tolerance.relative = 1e-11
+# Computed using scipy stats
+mean = 0
+variance = Infinity
+cdf.points = \
+  -2, -1, 0, 1, 2, 3, 4, 5
+cdf.values = \
+  0.09175170953613696,  0.21132486540518713,  0.5                ,\
+  0.78867513459481287,  0.90824829046386302,  0.95226701686664539,\
+  0.97140452079103168,  0.98112522432468807
+pdf.values = \
+  0.06804138174397717,  0.19245008972987523,  0.35355339059327379,\
+  0.19245008972987523,  0.06804138174397717,  0.02741012223434215,\
+  0.0130945700219731 ,  0.00712778110110649
+sf.values = \
+0.90824829046386302,  0.78867513459481287,  0.5                ,\
+  0.21132486540518713,  0.09175170953613696,  0.04773298313335456,\
+  0.02859547920896831,  0.01887477567531186
diff --git 
a/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.t.4.properties
 
b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.t.4.properties
new file mode 100644
index 0000000..a44fda5
--- /dev/null
+++ 
b/commons-statistics-distribution/src/test/resources/org/apache/commons/statistics/distribution/test.t.4.properties
@@ -0,0 +1,36 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Special case for large degrees of freedom is the standard normal distribution
+parameters = Infinity
+mean = 0
+variance = 1
+# normal(0, 1) computed using R
+cdf.points = \
+  -2, -1, 0, 1, 2, 3, 4, 5
+cdf.values = \
+  0.022750131948179212055 0.158655253931457046468 0.500000000000000000000 \
+  0.841344746068542925777 0.977249868051820791415 0.998650101968369896532 \
+  0.999968328758166880021 0.999999713348428076465
+pdf.values = \
+  5.3990966513188062836e-02 2.4197072451914336533e-01 \
+  3.9894228040143270286e-01 2.4197072451914336533e-01 \
+  5.3990966513188062836e-02 4.4318484119380075273e-03 \
+  1.3383022576488536764e-04 1.4867195147342976779e-06
+sf.values =  \
+  9.7724986805182079141e-01 8.4134474606854292578e-01 \
+  5.0000000000000000000e-01 1.5865525393145704647e-01 \
+  2.2750131948179212055e-02 1.3498980316300945772e-03 \
+  3.1671241833119924327e-05 2.8665157187919391185e-07

Reply via email to