FirstMoment.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.statistics.descriptive;

  18. import java.util.function.DoubleConsumer;

  19. /**
  20.  * Computes the first moment (arithmetic mean) using the definitional formula:
  21.  *
  22.  * <pre>mean = sum(x_i) / n</pre>
  23.  *
  24.  * <p> To limit numeric errors, the value of the statistic is computed using the
  25.  * following recursive updating algorithm:
  26.  * <ol>
  27.  * <li>Initialize {@code m = } the first value</li>
  28.  * <li>For each additional value, update using <br>
  29.  *   {@code m = m + (new value - m) / (number of observations)}</li>
  30.  * </ol>
  31.  *
  32.  * <p>Returns {@code NaN} if the dataset is empty. Note that
  33.  * {@code NaN} may also be returned if the input includes {@code NaN} and / or infinite
  34.  * values of opposite sign.
  35.  *
  36.  * <p>Supports up to 2<sup>63</sup> (exclusive) observations.
  37.  * This implementation does not check for overflow of the count.
  38.  *
  39.  * <p><strong>Note that this implementation is not synchronized.</strong> If
  40.  * multiple threads access an instance of this class concurrently, and at least
  41.  * one of the threads invokes the {@link java.util.function.DoubleConsumer#accept(double) accept} or
  42.  * {@link StatisticAccumulator#combine(StatisticResult) combine} method, it must be synchronized externally.
  43.  *
  44.  * <p>However, it is safe to use {@link java.util.function.DoubleConsumer#accept(double) accept}
  45.  * and {@link StatisticAccumulator#combine(StatisticResult) combine}
  46.  * as {@code accumulator} and {@code combiner} functions of
  47.  * {@link java.util.stream.Collector Collector} on a parallel stream,
  48.  * because the parallel implementation of {@link java.util.stream.Stream#collect Stream.collect()}
  49.  * provides the necessary partitioning, isolation, and merging of results for
  50.  * safe and efficient parallel execution.
  51.  *
  52.  * <p>References:
  53.  * <ul>
  54.  *   <li>Chan, Golub, Levesque (1983)
  55.  *       Algorithms for Computing the Sample Variance.
  56.  *       American Statistician, vol. 37, no. 3, pp. 242-247.
  57.  *   <li>Ling (1974)
  58.  *       Comparison of Several Algorithms for Computing Sample Means and Variances.
  59.  *       Journal of the American Statistical Association, Vol. 69, No. 348, pp. 859-866.
  60.  * </ul>
  61.  *
  62.  * @since 1.1
  63.  */
  64. class FirstMoment implements DoubleConsumer {
  65.     /** The downscale constant. Used to avoid overflow for all finite input. */
  66.     private static final double DOWNSCALE = 0.5;
  67.     /** The rescale constant. */
  68.     private static final double RESCALE = 2;

  69.     /** Count of values that have been added. */
  70.     protected long n;

  71.     /**
  72.      * Half the deviation of most recently added value from the previous first moment.
  73.      * Retained to prevent repeated computation in higher order moments.
  74.      *
  75.      * <p>Note: This is (x - m1) / 2. It is computed as a half value to prevent overflow
  76.      * when computing for any finite value x and m.
  77.      *
  78.      * <p>This value is not used in the {@link #combine(FirstMoment)} method.
  79.      */
  80.     protected double dev;

  81.     /**
  82.      * Half the deviation of most recently added value from the previous first moment,
  83.      * normalized by current sample size. Retained to prevent repeated
  84.      * computation in higher order moments.
  85.      *
  86.      * <p>Note: This is (x - m1) / 2n. It is computed as a half value to prevent overflow
  87.      * when computing for any finite value x and m.
  88.      *
  89.      * Note: This value is not used in the {@link #combine(FirstMoment)} method.
  90.      */
  91.     protected double nDev;

  92.     /** First moment of values that have been added.
  93.      * This is stored as a half value to prevent overflow for any finite input.
  94.      * Benchmarks show this has negligible performance impact. */
  95.     private double m1;

  96.     /**
  97.      * Running sum of values seen so far.
  98.      * This is not used in the computation of mean. Used as a return value for first moment when
  99.      * it is non-finite.
  100.      */
  101.     private double nonFiniteValue;

  102.     /**
  103.      * Create an instance.
  104.      */
  105.     FirstMoment() {
  106.         // No-op
  107.     }

  108.     /**
  109.      * Copy constructor.
  110.      *
  111.      * @param source Source to copy.
  112.      */
  113.     FirstMoment(FirstMoment source) {
  114.         m1 = source.m1;
  115.         n = source.n;
  116.         nonFiniteValue = source.nonFiniteValue;
  117.     }

  118.     /**
  119.      * Create an instance with the given first moment.
  120.      *
  121.      * <p>This constructor is used when creating the moment from a finite sum of values.
  122.      *
  123.      * @param m1 First moment.
  124.      * @param n Count of values.
  125.      */
  126.     FirstMoment(double m1, long n) {
  127.         this.m1 = m1 * DOWNSCALE;
  128.         this.n = n;
  129.     }

  130.     /**
  131.      * Returns an instance populated using the input {@code values}.
  132.      *
  133.      * <p>Note: {@code FirstMoment} computed using {@link #accept} may be different from
  134.      * this instance.
  135.      *
  136.      * @param values Values.
  137.      * @return {@code FirstMoment} instance.
  138.      */
  139.     static FirstMoment of(double... values) {
  140.         if (values.length == 0) {
  141.             return new FirstMoment();
  142.         }
  143.         // In the typical use-case a sum of values will not overflow and
  144.         // is faster than the rolling algorithm
  145.         return createFromRange(org.apache.commons.numbers.core.Sum.of(values), values, 0, values.length);
  146.     }

  147.     /**
  148.      * Returns an instance populated using the specified range of {@code values}.
  149.      *
  150.      * <p>Note: {@code FirstMoment} computed using {@link #accept} may be different from
  151.      * this instance.
  152.      *
  153.      * <p>Warning: No range checks are performed.
  154.      *
  155.      * @param values Values.
  156.      * @param from Inclusive start of the range.
  157.      * @param to Exclusive end of the range.
  158.      * @return {@code FirstMoment} instance.
  159.      * @throws IndexOutOfBoundsException if the sub-range is out of bounds
  160.      * @since 1.2
  161.      */
  162.     static FirstMoment ofRange(double[] values, int from, int to) {
  163.         if (from == to) {
  164.             return new FirstMoment();
  165.         }
  166.         return createFromRange(Statistics.sum(values, from, to), values, from, to);
  167.     }

  168.     /**
  169.      * Creates the first moment.
  170.      *
  171.      * <p>Uses the provided {@code sum} if finite; otherwise reverts to using the rolling moment
  172.      * to protect from overflow and adds a second pass correction term.
  173.      *
  174.      * <p>This method is used by {@link DoubleStatistics} using a sum that can be reused
  175.      * for the {@link Sum} statistic.
  176.      *
  177.      * <p>Warning: No range checks are performed.
  178.      *
  179.      * @param sum Sum of the values.
  180.      * @param values Values.
  181.      * @param from Inclusive start of the range.
  182.      * @param to Exclusive end of the range.
  183.      * @return {@code FirstMoment} instance.
  184.      */
  185.     static FirstMoment createFromRange(org.apache.commons.numbers.core.Sum sum,
  186.                                        double[] values, int from, int to) {
  187.         // Protect against empty values
  188.         if (from == to) {
  189.             return new FirstMoment();
  190.         }

  191.         final double s = sum.getAsDouble();
  192.         if (Double.isFinite(s)) {
  193.             return new FirstMoment(s / (to - from), to - from);
  194.         }

  195.         // "Corrected two-pass algorithm"

  196.         // First pass
  197.         final FirstMoment m1 = create(values, from, to);
  198.         final double xbar = m1.getFirstMoment();
  199.         if (!Double.isFinite(xbar)) {
  200.             return m1;
  201.         }
  202.         // Second pass
  203.         double correction = 0;
  204.         for (int i = from; i < to; i++) {
  205.             correction += values[i] - xbar;
  206.         }
  207.         // Note: Correction may be infinite
  208.         if (Double.isFinite(correction)) {
  209.             // Down scale the correction to the half representation
  210.             m1.m1 += DOWNSCALE * correction / m1.n;
  211.         }
  212.         return m1;
  213.     }

  214.     /**
  215.      * Creates the first moment using a rolling algorithm.
  216.      *
  217.      * <p>This duplicates the algorithm in the {@link #accept(double)} method
  218.      * with optimisations due to the processing of an entire array:
  219.      * <ul>
  220.      *  <li>Avoid updating (unused) class level working variables.
  221.      *  <li>Only computing the non-finite value if required.
  222.      * </ul>
  223.      *
  224.      * @param values Values.
  225.      * @param from Inclusive start of the range.
  226.      * @param to Exclusive end of the range.
  227.      * @return the first moment
  228.      */
  229.     private static FirstMoment create(double[] values, int from, int to) {
  230.         double m1 = 0;
  231.         int n = 0;
  232.         for (int i = from; i < to; i++) {
  233.             // Downscale to avoid overflow for all finite input
  234.             m1 += (values[i] * DOWNSCALE - m1) / ++n;
  235.         }
  236.         final FirstMoment m = new FirstMoment();
  237.         m.n = n;
  238.         // Note: m1 is already downscaled here
  239.         m.m1 = m1;
  240.         // The non-finite value is only relevant if the data contains inf/nan
  241.         if (!Double.isFinite(m1 * RESCALE)) {
  242.             m.nonFiniteValue = computeNonFiniteValue(values, from, to);
  243.         }
  244.         return m;
  245.     }

  246.     /**
  247.      * Compute the result in the event of non-finite values.
  248.      *
  249.      * @param values Values.
  250.      * @param from Inclusive start of the range.
  251.      * @param to Exclusive end of the range.
  252.      * @return the non-finite result
  253.      */
  254.     private static double computeNonFiniteValue(double[] values, int from, int to) {
  255.         double sum = 0;
  256.         for (int i = from; i < to; i++) {
  257.             // Scaling down values prevents overflow of finites.
  258.             sum += values[i] * Double.MIN_NORMAL;
  259.         }
  260.         return sum;
  261.     }

  262.     /**
  263.      * Updates the state of the statistic to reflect the addition of {@code value}.
  264.      *
  265.      * @param value Value.
  266.      */
  267.     @Override
  268.     public void accept(double value) {
  269.         // "Updating one-pass algorithm"
  270.         // See: Chan et al (1983) Equation 1.3a
  271.         // m_{i+1} = m_i + (x - m_i) / (i + 1)
  272.         // This is modified with scaling to avoid overflow for all finite input.
  273.         // Scaling the input down by a factor of two ensures that the scaling is lossless.
  274.         // Sub-classes must alter their scaling factors when using the computed deviations.

  275.         // Note: Maintain the correct non-finite result.
  276.         // Scaling down values prevents overflow of finites.
  277.         nonFiniteValue += value * Double.MIN_NORMAL;
  278.         // Scale down the input
  279.         dev = value * DOWNSCALE - m1;
  280.         nDev = dev / ++n;
  281.         m1 += nDev;
  282.     }

  283.     /**
  284.      * Gets the first moment of all input values.
  285.      *
  286.      * <p>When no values have been added, the result is {@code NaN}.
  287.      *
  288.      * @return {@code First moment} of all values, if it is finite;
  289.      *         {@code +/-Infinity}, if infinities of the same sign have been encountered;
  290.      *         {@code NaN} otherwise.
  291.      */
  292.     double getFirstMoment() {
  293.         // Scale back to the original magnitude
  294.         final double m = m1 * RESCALE;
  295.         if (Double.isFinite(m)) {
  296.             return n == 0 ? Double.NaN : m;
  297.         }
  298.         // A non-finite value must have been encountered, return nonFiniteValue which represents m1.
  299.         return nonFiniteValue;
  300.     }

  301.     /**
  302.      * Combines the state of another {@code FirstMoment} into this one.
  303.      *
  304.      * @param other Another {@code FirstMoment} to be combined.
  305.      * @return {@code this} instance after combining {@code other}.
  306.      */
  307.     FirstMoment combine(FirstMoment other) {
  308.         nonFiniteValue += other.nonFiniteValue;
  309.         final double mu1 = this.m1;
  310.         final double mu2 = other.m1;
  311.         final long n1 = n;
  312.         final long n2 = other.n;
  313.         n = n1 + n2;
  314.         // Adjust the mean with the weighted difference:
  315.         // m1 = m1 + (m2 - m1) * n2 / (n1 + n2)
  316.         // The half-representation ensures the difference of means is at most MAX_VALUE
  317.         // so the combine can avoid scaling.
  318.         if (n1 == n2) {
  319.             // Optimisation for equal sizes: m1 = (m1 + m2) / 2
  320.             m1 = (mu1 + mu2) * 0.5;
  321.         } else {
  322.             m1 = combine(mu1, mu2, n1, n2);
  323.         }
  324.         return this;
  325.     }

  326.     /**
  327.      * Combine the moments. This method is used to enforce symmetry. It assumes that
  328.      * the two sizes are not identical, and at least one size is non-zero.
  329.      *
  330.      * @param m1 Moment 1.
  331.      * @param m2 Moment 2.
  332.      * @param n1 Size of sample 1.
  333.      * @param n2 Size of sample 2.
  334.      * @return the combined first moment
  335.      */
  336.     private static double combine(double m1, double m2, long n1, long n2) {
  337.         // Note: If either size is zero the weighted difference is zero and
  338.         // the other moment is unchanged.
  339.         return n2 < n1 ?
  340.             m1 + (m2 - m1) * ((double) n2 / (n1 + n2)) :
  341.             m2 + (m1 - m2) * ((double) n1 / (n1 + n2));
  342.     }

  343.     /**
  344.      * Gets the difference of the first moment between {@code this} moment and the
  345.      * {@code other} moment. This is provided for sub-classes.
  346.      *
  347.      * @param other Other moment.
  348.      * @return the difference
  349.      */
  350.     double getFirstMomentDifference(FirstMoment other) {
  351.         // Scale back to the original magnitude
  352.         return (m1 - other.m1) * RESCALE;
  353.     }

  354.     /**
  355.      * Gets the half the difference of the first moment between {@code this} moment and
  356.      * the {@code other} moment. This is provided for sub-classes.
  357.      *
  358.      * @param other Other moment.
  359.      * @return the difference
  360.      */
  361.     double getFirstMomentHalfDifference(FirstMoment other) {
  362.         return m1 - other.m1;
  363.     }
  364. }