Statistics.java

/*
 * 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.
 */
package org.apache.commons.math4.legacy.stat.descriptive;

import java.util.function.BinaryOperator;
import java.util.function.Supplier;
import org.apache.commons.math4.legacy.core.MathArrays;
import org.apache.commons.math4.legacy.exception.MathIllegalArgumentException;
import org.apache.commons.math4.legacy.exception.OutOfRangeException;
import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
import org.apache.commons.statistics.descriptive.DoubleStatistic;
import org.apache.commons.statistics.descriptive.Quantile;
import org.apache.commons.statistics.descriptive.StatisticResult;
import org.apache.commons.statistics.descriptive.Quantile.EstimationMethod;

/**
 * Utility class delegating computations to Commons Statistics.
 */
final class Statistics {

    /**
     * Represents a function that accepts a range of {@code double[]} values and produces a result.
     *
     * @param <R> the type of the result of the function
     */
    @FunctionalInterface
    private interface RangeFunction<R> {
        /**
         * Applies this function to the given arguments.
         *
         * @param t the function argument
         * @param from Inclusive start of the range.
         * @param to Exclusive end of the range.
         * @return the function result
         */
        R apply(double[] t, int from, int to);
    }

    /**
     * Delegating univariate statistic implementation.
     * This is the base class for descriptive statistics computed using an array range.
     *
     * @param <R> the statistic type
     */
    private static class StatImp<R extends DoubleStatistic> implements UnivariateStatistic {
        /** Statistic function. */
        private final RangeFunction<R> function;

        /**
         * Create an instance.
         *
         * @param function the function
         */
        StatImp(RangeFunction<R> function) {
            this.function = function;
        }

        @Override
        public double evaluate(double[] values) {
            // This method is not used
            throw new IllegalStateException();
        }

        @Override
        public double evaluate(double[] values, int begin, int length) {
            // Support legacy exception behaviour
            MathArrays.verifyValues(values, begin, length);
            return function.apply(values, begin, begin + length).getAsDouble();
        }

        @Override
        public UnivariateStatistic copy() {
            // Return this is safe if the RangeFunction is thread safe
            return this;
        }
    }

    /**
     * Delegating univariate statistic implementation that returns NaN for empty arrays.
     *
     * @param <R> the statistic type
     */
    private static class NaNStatImp<R extends DoubleStatistic> extends StatImp<R> {
        /**
         * Create an instance.
         *
         * @param function the function
         */
        NaNStatImp(RangeFunction<R> function) {
            super(function);
        }

        @Override
        public double evaluate(double[] values, int begin, int length) {
            // Support legacy behaviour of returning NaN for empty data
            final double r = super.evaluate(values, begin, length);
            return length == 0 ? Double.NaN : r;
        }
    }

    /**
     * Mean implementation.
     */
    static final class Mean extends StatImp<org.apache.commons.statistics.descriptive.Mean> {
        /** Default instance. */
        private static final Mean INSTANCE = new Mean();

        /** Create an instance. */
        private Mean() {
            super(org.apache.commons.statistics.descriptive.Mean::ofRange);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static Mean getInstance() {
            return INSTANCE;
        }
    }

    /**
     * GeometricMean implementation.
     */
    static final class GeometricMean extends StatImp<org.apache.commons.statistics.descriptive.GeometricMean> {
        /** Default instance. */
        private static final GeometricMean INSTANCE = new GeometricMean();

        /** Create an instance. */
        private GeometricMean() {
            super(org.apache.commons.statistics.descriptive.GeometricMean::ofRange);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static GeometricMean getInstance() {
            return INSTANCE;
        }
    }

    /**
     * Kurtosis implementation.
     */
    static final class Kurtosis extends StatImp<org.apache.commons.statistics.descriptive.Kurtosis> {
        /** Default instance. */
        private static final Kurtosis INSTANCE = new Kurtosis();

        /** Create an instance. */
        private Kurtosis() {
            super(org.apache.commons.statistics.descriptive.Kurtosis::ofRange);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static Kurtosis getInstance() {
            return INSTANCE;
        }
    }

    /**
     * Max implementation.
     */
    static final class Max extends NaNStatImp<org.apache.commons.statistics.descriptive.Max> {
        /** Default instance. */
        private static final Max INSTANCE = new Max();

        /** Create an instance. */
        private Max() {
            super(org.apache.commons.statistics.descriptive.Max::ofRange);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static Max getInstance() {
            return INSTANCE;
        }
    }

    /**
     * Min implementation.
     */
    static final class Min extends NaNStatImp<org.apache.commons.statistics.descriptive.Min> {
        /** Default instance. */
        private static final Min INSTANCE = new Min();

        /** Create an instance. */
        private Min() {
            super(org.apache.commons.statistics.descriptive.Min::ofRange);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static Min getInstance() {
            return INSTANCE;
        }
    }

    /**
     * Skewness implementation.
     */
    static final class Skewness extends StatImp<org.apache.commons.statistics.descriptive.Skewness> {
        /** Default instance. */
        private static final Skewness INSTANCE = new Skewness();

        /** Create an instance. */
        private Skewness() {
            super(org.apache.commons.statistics.descriptive.Skewness::ofRange);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static Skewness getInstance() {
            return INSTANCE;
        }
    }

    /**
     * Variance implementation.
     */
    static final class Variance extends StatImp<org.apache.commons.statistics.descriptive.Variance> {
        /** Default instance. */
        private static final Variance INSTANCE = new Variance();

        /** Create an instance. */
        private Variance() {
            super(org.apache.commons.statistics.descriptive.Variance::ofRange);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static Variance getInstance() {
            return INSTANCE;
        }
    }

    /**
     * SumOfSquares implementation.
     */
    static final class SumOfSquares extends NaNStatImp<org.apache.commons.statistics.descriptive.SumOfSquares> {
        /** Default instance. */
        private static final SumOfSquares INSTANCE = new SumOfSquares();

        /** Create an instance. */
        private SumOfSquares() {
            super(org.apache.commons.statistics.descriptive.SumOfSquares::ofRange);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static SumOfSquares getInstance() {
            return INSTANCE;
        }
    }

    /**
     * Sum implementation.
     */
    static final class Sum extends NaNStatImp<org.apache.commons.statistics.descriptive.Sum> {
        /** Default instance. */
        private static final Sum INSTANCE = new Sum();

        /** Create an instance. */
        private Sum() {
            super(org.apache.commons.statistics.descriptive.Sum::ofRange);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static Sum getInstance() {
            return INSTANCE;
        }
    }

    /**
     * Percentile implementation.
     */
    static final class Percentile implements UnivariateStatistic {
        /** Delegate percentile implementation. */
        private static final Quantile QUANTILE =
            Quantile.withDefaults().with(EstimationMethod.HF6).withCopy(false);

        /** Probability. */
        private double p;

        /**
         * Create an instance.
         *
         * @param p Probability in [0, 1].
         */
        private Percentile(double p) {
            this.p = p;
        }

        /**
         * Create an instance.
         *
         * @param percentile Percentile in [0, 100].
         * @return an instance
         * @throws OutOfRangeException if the percentile is invalid.
         */
        static Percentile create(double percentile) {
            return new Percentile(createProbability(percentile));
        }

        /**
         * Create the probability from the percentile.
         *
         * @param percentile Percentile in [0, 100].
         * @return probability in [0, 1]
         * @throws OutOfRangeException if the percentile is invalid.
         */
        static double createProbability(double percentile) {
            if (percentile <= 100 && percentile >= 0) {
                return percentile / 100;
            }
            // NaN or out of range
            throw new OutOfRangeException(LocalizedFormats.OUT_OF_RANGE, percentile, 0, 100);
        }

        /**
         * Sets the quantile (in percent).
         * Note: This is named using the CM legacy method name
         * setQuantile rather than setPercentile.
         *
         * @param percentile Percentile in [0, 100].
         * @throws OutOfRangeException if the percentile is invalid.
         */
        void setQuantile(double percentile) {
            p = createProbability(percentile);
        }

        @Override
        public double evaluate(double[] values) {
            MathArrays.verifyValues(values, 0, 0);
            return QUANTILE.evaluate(values, p);
        }

        @Override
        public double evaluate(double[] values, int begin, int length) {
            MathArrays.verifyValues(values, begin, length);
            return QUANTILE.evaluateRange(values, begin, begin + length, p);
        }

        @Override
        public UnivariateStatistic copy() {
            return new Percentile(p);
        }
    }

    /**
     * Delegating storeless univariate statistic implementation.
     * This is the base class for descriptive statistics computed using a single double value.
     *
     * @param <R> the statistic type
     */
    private static class StorelessStatImp<R extends DoubleStatistic & StatisticResult> implements StorelessUnivariateStatistic {
        /** Statistic factory function. */
        private final Supplier<R> factory;
        /** Statistic combine function. */
        private final BinaryOperator<R> combine;
        /** Statistic. */
        private R s;

        /**
         * Create an instance.
         *
         * @param factory the factory function
         * @param combine the combine function
         */
        StorelessStatImp(Supplier<R> factory, BinaryOperator<R> combine) {
            this.factory = factory;
            this.combine = combine;
            s = factory.get();
        }

        @Override
        public void increment(double d) {
            s.accept(d);
        }

        @Override
        public void incrementAll(double[] values) throws MathIllegalArgumentException {
            // This method is not used
            throw new IllegalStateException();
        }

        @Override
        public void incrementAll(double[] values, int start, int length) throws MathIllegalArgumentException {
            // This method is not used
            throw new IllegalStateException();
        }

        @Override
        public double getResult() {
            return s.getAsDouble();
        }

        @Override
        public long getN() {
            // This method is not used
            throw new IllegalStateException();
        }

        @Override
        public void clear() {
            s = factory.get();
        }

        @Override
        public StorelessUnivariateStatistic copy() {
            final StorelessStatImp<R> r = new StorelessStatImp<R>(factory, combine);
            r.s = combine.apply(r.s, s);
            return r;
        }

        @Override
        public double evaluate(double[] values) throws MathIllegalArgumentException {
            // This method is not used
            throw new IllegalStateException();
        }

        @Override
        public double evaluate(double[] values, int begin, int length) throws MathIllegalArgumentException {
            // This method is not used
            throw new IllegalStateException();
        }
    }

    /**
     * Storeless Sum implementation.
     */
    static final class StorelessSum extends StorelessStatImp<org.apache.commons.statistics.descriptive.Sum> {
        /** Create an instance. */
        private StorelessSum() {
            super(org.apache.commons.statistics.descriptive.Sum::create, org.apache.commons.statistics.descriptive.Sum::combine);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static StorelessSum create() {
            return new StorelessSum();
        }
    }

    /**
     * Storeless SumOfSquares implementation.
     */
    static final class StorelessSumOfSquares extends StorelessStatImp<org.apache.commons.statistics.descriptive.SumOfSquares> {
        /** Create an instance. */
        private StorelessSumOfSquares() {
            super(org.apache.commons.statistics.descriptive.SumOfSquares::create, org.apache.commons.statistics.descriptive.SumOfSquares::combine);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static StorelessSumOfSquares create() {
            return new StorelessSumOfSquares();
        }
    }

    /**
     * Storeless Min implementation.
     */
    static final class StorelessMin extends StorelessStatImp<org.apache.commons.statistics.descriptive.Min> {
        /** Create an instance. */
        private StorelessMin() {
            super(org.apache.commons.statistics.descriptive.Min::create, org.apache.commons.statistics.descriptive.Min::combine);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static StorelessMin create() {
            return new StorelessMin();
        }
    }

    /**
     * Storeless Max implementation.
     */
    static final class StorelessMax extends StorelessStatImp<org.apache.commons.statistics.descriptive.Max> {
        /** Create an instance. */
        private StorelessMax() {
            super(org.apache.commons.statistics.descriptive.Max::create, org.apache.commons.statistics.descriptive.Max::combine);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static StorelessMax create() {
            return new StorelessMax();
        }
    }

    /**
     * Storeless SumOfLogs implementation.
     */
    static final class StorelessSumOfLogs extends StorelessStatImp<org.apache.commons.statistics.descriptive.SumOfLogs> {
        /** Create an instance. */
        private StorelessSumOfLogs() {
            super(org.apache.commons.statistics.descriptive.SumOfLogs::create, org.apache.commons.statistics.descriptive.SumOfLogs::combine);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static StorelessSumOfLogs create() {
            return new StorelessSumOfLogs();
        }
    }

    /**
     * Storeless GeometricMean implementation.
     */
    static final class StorelessGeometricMean extends StorelessStatImp<org.apache.commons.statistics.descriptive.GeometricMean> {
        /** Create an instance. */
        private StorelessGeometricMean() {
            super(org.apache.commons.statistics.descriptive.GeometricMean::create, org.apache.commons.statistics.descriptive.GeometricMean::combine);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static StorelessGeometricMean create() {
            return new StorelessGeometricMean();
        }
    }

    /**
     * Storeless Mean implementation.
     */
    static final class StorelessMean extends StorelessStatImp<org.apache.commons.statistics.descriptive.Mean> {
        /** Create an instance. */
        private StorelessMean() {
            super(org.apache.commons.statistics.descriptive.Mean::create, org.apache.commons.statistics.descriptive.Mean::combine);
        }

        /**
         * Gets an instance.
         *
         * @return instance
         */
        static StorelessMean create() {
            return new StorelessMean();
        }
    }

    /** No instances. */
    private Statistics() {
        // Do nothing
    }
}