Statistics.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. import java.util.function.IntConsumer;
  20. import java.util.function.LongConsumer;

  21. /**
  22.  * Utility methods for statistics.
  23.  *
  24.  * @since 1.1
  25.  */
  26. final class Statistics {
  27.     /** A no-operation double consumer. This is exposed for testing. */
  28.     static final DoubleConsumer DOUBLE_NOOP = new DoubleConsumer() {
  29.         @Override
  30.         public void accept(double value) {
  31.             // Do nothing
  32.         }

  33.         @Override
  34.         public DoubleConsumer andThen(DoubleConsumer after) {
  35.             // Delegate to the after consumer
  36.             return after;
  37.         }
  38.     };

  39.     /** A no-operation int consumer. This is exposed for testing. */
  40.     static final IntConsumer INT_NOOP = new IntConsumer() {
  41.         @Override
  42.         public void accept(int value) {
  43.             // Do nothing
  44.         }

  45.         @Override
  46.         public IntConsumer andThen(IntConsumer after) {
  47.             // Delegate to the after consumer
  48.             return after;
  49.         }
  50.     };

  51.     /** A no-operation long consumer. This is exposed for testing. */
  52.     static final LongConsumer LONG_NOOP = new LongConsumer() {
  53.         @Override
  54.         public void accept(long value) {
  55.             // Do nothing
  56.         }

  57.         @Override
  58.         public LongConsumer andThen(LongConsumer after) {
  59.             // Delegate to the after consumer
  60.             return after;
  61.         }
  62.     };

  63.     /** Error message for an incompatible statistics. */
  64.     private static final String INCOMPATIBLE_STATISTICS = "Incompatible statistics";

  65.     /** No instances. */
  66.     private Statistics() {}

  67.     /**
  68.      * Add all the {@code values} to the {@code statistic}.
  69.      *
  70.      * @param <T> Type of the statistic
  71.      * @param statistic Statistic.
  72.      * @param values Values.
  73.      * @return the statistic
  74.      */
  75.     static <T extends DoubleConsumer> T add(T statistic, double[] values) {
  76.         for (final double x : values) {
  77.             statistic.accept(x);
  78.         }
  79.         return statistic;
  80.     }

  81.     /**
  82.      * Add all the {@code values} to the {@code statistic}.
  83.      *
  84.      * @param <T> Type of the statistic
  85.      * @param statistic Statistic.
  86.      * @param values Values.
  87.      * @return the statistic
  88.      */
  89.     static <T extends DoubleConsumer> T add(T statistic, int[] values) {
  90.         for (final double x : values) {
  91.             statistic.accept(x);
  92.         }
  93.         return statistic;
  94.     }

  95.     /**
  96.      * Add all the {@code values} to the {@code statistic}.
  97.      *
  98.      * @param <T> Type of the statistic
  99.      * @param statistic Statistic.
  100.      * @param values Values.
  101.      * @return the statistic
  102.      */
  103.     static <T extends DoubleConsumer> T add(T statistic, long[] values) {
  104.         for (final double x : values) {
  105.             statistic.accept(x);
  106.         }
  107.         return statistic;
  108.     }

  109.     /**
  110.      * Add all the {@code values} to the {@code statistic}.
  111.      *
  112.      * @param <T> Type of the statistic
  113.      * @param statistic Statistic.
  114.      * @param values Values.
  115.      * @return the statistic
  116.      */
  117.     static <T extends IntConsumer> T add(T statistic, int[] values) {
  118.         for (final int x : values) {
  119.             statistic.accept(x);
  120.         }
  121.         return statistic;
  122.     }

  123.     /**
  124.      * Add all the {@code values} to the {@code statistic}.
  125.      *
  126.      * @param <T> Type of the statistic
  127.      * @param statistic Statistic.
  128.      * @param values Values.
  129.      * @return the statistic
  130.      */
  131.     static <T extends LongConsumer> T add(T statistic, long[] values) {
  132.         for (final long x : values) {
  133.             statistic.accept(x);
  134.         }
  135.         return statistic;
  136.     }

  137.     /**
  138.      * Returns {@code true} if the second central moment {@code m2} is effectively
  139.      * zero given the magnitude of the first raw moment {@code m1}.
  140.      *
  141.      * <p>This method shares the logic for detecting a zero variance among implementations
  142.      * that divide by the variance (e.g. skewness, kurtosis).
  143.      *
  144.      * @param m1 First raw moment (mean).
  145.      * @param m2 Second central moment (biased variance).
  146.      * @return true if the variance is zero
  147.      */
  148.     static boolean zeroVariance(double m1, double m2) {
  149.         // Note: Commons Math checks the variance is < 1e-19.
  150.         // The absolute threshold does not account for the magnitude of the sample.
  151.         // This checks the average squared deviation from the mean (m2)
  152.         // is smaller than the squared precision of the mean (m1).
  153.         // Precision is set to 15 decimal digits
  154.         // (1e-15 ~ 4.5 eps where eps = 2^-52).
  155.         final double meanPrecision = 1e-15 * m1;
  156.         return m2 <= meanPrecision * meanPrecision;
  157.     }

  158.     /**
  159.      * Chain the {@code consumers} into a single composite consumer. Ignore any {@code null}
  160.      * consumer. Returns {@code null} if all arguments are {@code null}.
  161.      *
  162.      * @param consumers Consumers.
  163.      * @return a composed consumer (or null)
  164.      */
  165.     static DoubleConsumer compose(DoubleConsumer... consumers) {
  166.         DoubleConsumer action = DOUBLE_NOOP;
  167.         for (final DoubleConsumer consumer : consumers) {
  168.             if (consumer != null) {
  169.                 action = action.andThen(consumer);
  170.             }
  171.         }
  172.         return action == DOUBLE_NOOP ? null : action;
  173.     }

  174.     /**
  175.      * Chain the {@code consumers} into a single composite consumer. Ignore any {@code null}
  176.      * consumer. Returns {@code null} if all arguments are {@code null}.
  177.      *
  178.      * @param consumers Consumers.
  179.      * @return a composed consumer (or null)
  180.      */
  181.     static IntConsumer compose(IntConsumer... consumers) {
  182.         IntConsumer action = INT_NOOP;
  183.         for (final IntConsumer consumer : consumers) {
  184.             if (consumer != null) {
  185.                 action = action.andThen(consumer);
  186.             }
  187.         }
  188.         return action == INT_NOOP ? null : action;
  189.     }

  190.     /**
  191.      * Chain the {@code consumers} into a single composite consumer. Ignore any {@code null}
  192.      * consumer. Returns {@code null} if all arguments are {@code null}.
  193.      *
  194.      * @param consumers Consumers.
  195.      * @return a composed consumer (or null)
  196.      */
  197.     static LongConsumer compose(LongConsumer... consumers) {
  198.         LongConsumer action = LONG_NOOP;
  199.         for (final LongConsumer consumer : consumers) {
  200.             if (consumer != null) {
  201.                 action = action.andThen(consumer);
  202.             }
  203.         }
  204.         return action == LONG_NOOP ? null : action;
  205.     }

  206.     /**
  207.      * Gets the statistic result using the {@code int} value.
  208.      * Return {@code null} is the statistic is {@code null}.
  209.      *
  210.      * @param s Statistic.
  211.      * @return the result or null
  212.      */
  213.     static StatisticResult getResultAsIntOrNull(StatisticResult s) {
  214.         if (s != null) {
  215.             return (IntStatisticResult) s::getAsInt;
  216.         }
  217.         return null;
  218.     }

  219.     /**
  220.      * Gets the statistic result using the {@code long} value.
  221.      * Return {@code null} is the statistic is {@code null}.
  222.      *
  223.      * @param s Statistic.
  224.      * @return the result or null
  225.      */
  226.     static StatisticResult getResultAsLongOrNull(StatisticResult s) {
  227.         if (s != null) {
  228.             return (LongStatisticResult) s::getAsLong;
  229.         }
  230.         return null;
  231.     }

  232.     /**
  233.      * Gets the statistic result using the {@code double} value.
  234.      * Return {@code null} is the statistic is {@code null}.
  235.      *
  236.      * @param s Statistic.
  237.      * @return the result or null
  238.      */
  239.     static StatisticResult getResultAsDoubleOrNull(StatisticResult s) {
  240.         if (s != null) {
  241.             return s::getAsDouble;
  242.         }
  243.         return null;
  244.     }

  245.     /**
  246.      * Gets the statistic result using the {@code BigInteger} value.
  247.      * Return {@code null} is the statistic is {@code null}.
  248.      *
  249.      * @param s Statistic.
  250.      * @return the result or null
  251.      */
  252.     static StatisticResult getResultAsBigIntegerOrNull(StatisticResult s) {
  253.         if (s != null) {
  254.             return (BigIntegerStatisticResult) s::getAsBigInteger;
  255.         }
  256.         return null;
  257.     }

  258.     /**
  259.      * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
  260.      * argument {@code b} must also be non-{@code null} so the statistics can be combined.
  261.      *
  262.      * @param <T> {@link StatisticResult} being accumulated.
  263.      * @param a LHS.
  264.      * @param b RHS.
  265.      * @throws IllegalArgumentException if the objects cannot be combined
  266.      */
  267.     static <T extends StatisticResult & StatisticAccumulator<T>> void checkCombineCompatible(T a, T b) {
  268.         if (a != null && b == null) {
  269.             throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
  270.         }
  271.     }

  272.     /**
  273.      * Check left-hand side argument {@code a} is {@code null} or else the right-hand side
  274.      * argument {@code b} must be run-time assignable to the same class as {@code a}
  275.      * so the statistics can be combined.
  276.      *
  277.      * @param a LHS.
  278.      * @param b RHS.
  279.      * @throws IllegalArgumentException if the objects cannot be combined
  280.      */
  281.     static void checkCombineAssignable(FirstMoment a, FirstMoment b) {
  282.         if (a != null && (b == null || !a.getClass().isAssignableFrom(b.getClass()))) {
  283.             throw new IllegalArgumentException(INCOMPATIBLE_STATISTICS);
  284.         }
  285.     }

  286.     /**
  287.      * If the left-hand side argument {@code a} is non-{@code null}, combine it with the
  288.      * right-hand side argument {@code b}.
  289.      *
  290.      * @param <T> {@link StatisticResult} being accumulated.
  291.      * @param a LHS.
  292.      * @param b RHS.
  293.      */
  294.     static <T extends StatisticResult & StatisticAccumulator<T>> void combine(T a, T b) {
  295.         if (a != null) {
  296.             a.combine(b);
  297.         }
  298.     }

  299.     /**
  300.      * If the left-hand side argument {@code a} is non-{@code null}, combine it with the
  301.      * right-hand side argument {@code b}. Assumes that the RHS is run-time assignable
  302.      * to the same class as LHS.
  303.      *
  304.      * @param a LHS.
  305.      * @param b RHS.
  306.      * @see #checkCombineAssignable(FirstMoment, FirstMoment)
  307.      */
  308.     static void combineMoment(FirstMoment a, FirstMoment b) {
  309.         // Avoid reflection and use the simpler instanceof
  310.         if (a instanceof SumOfFourthDeviations) {
  311.             ((SumOfFourthDeviations) a).combine((SumOfFourthDeviations) b);
  312.         } else if (a instanceof SumOfCubedDeviations) {
  313.             ((SumOfCubedDeviations) a).combine((SumOfCubedDeviations) b);
  314.         } else if (a instanceof SumOfSquaredDeviations) {
  315.             ((SumOfSquaredDeviations) a).combine((SumOfSquaredDeviations) b);
  316.         } else if (a != null) {
  317.             a.combine(b);
  318.         }
  319.     }
  320. }