package org.djutils.stats.summarizers; import org.djutils.exceptions.Throw; import org.djutils.stats.ConfidenceInterval; import org.djutils.stats.DistNormalTable; import org.djutils.stats.summarizers.quantileaccumulator.NoStorageAccumulator; import org.djutils.stats.summarizers.quantileaccumulator.QuantileAccumulator; /** * The Tally class ingests a series of values and provides mean, standard deviation, etc. of the ingested values. *

* Copyright (c) 2002-2021 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See * for project information https://simulation.tudelft.nl. The DSOL * project is distributed under a three-clause BSD-style license, which can be found at * * https://simulation.tudelft.nl/dsol/3.0/license.html.
* @author Alexander Verbraeck * @author Peter Jacobs * @author Peter Knoppers */ public class Tally implements TallyInterface { /** */ private static final long serialVersionUID = 20200228L; /** The sum of this tally. */ private double sum = 0; /** The mean of this tally. */ private double m1 = 0; /** The summation for the second moment (variance). */ private double m2 = 0; /** The summation for the third moment (skewness). */ private double m3 = 0; /** The summation for the fourth moment (kurtosis). */ private double m4 = 0; /** The minimum observed value of this tally. */ private double min = Double.NaN; /** The maximum observed value of this tally. */ private double max = Double.NaN; /** The number of measurements of this tally. */ private long n = 0; /** The description of this tally. */ private final String description; /** The quantile accumulator. */ private final QuantileAccumulator quantileAccumulator; /** the synchronized lock. */ @SuppressWarnings("checkstyle:visibilitymodifier") protected Object semaphore = new Object(); /** * Constructs a new Tally. * @param description String; the description of this tally * @param quantileAccumulator QuantileAccumulator; the input series accumulator that can approximate or compute quantiles. */ public Tally(final String description, final QuantileAccumulator quantileAccumulator) { this.description = description; this.quantileAccumulator = quantileAccumulator; initialize(); } /** * Convenience constructor that uses a NoStorageAccumulator to estimate quantiles. * @param description String; the description of this tally */ public Tally(final String description) { this(description, new NoStorageAccumulator()); } /** {@inheritDoc} */ @Override public final double getSampleMean() { if (this.n > 0) { return this.m1; } return Double.NaN; } /** {@inheritDoc} */ @Override public final double getQuantile(final double probability) { return this.quantileAccumulator.getQuantile(this, probability); } /** {@inheritDoc} */ @Override public final double[] getConfidenceInterval(final double alpha) { return this.getConfidenceInterval(alpha, ConfidenceInterval.BOTH_SIDE_CONFIDENCE); } /** {@inheritDoc} */ @Override public final double[] getConfidenceInterval(final double alpha, final ConfidenceInterval side) { Throw.whenNull(side, "type of confidence level cannot be null"); Throw.when(alpha < 0 || alpha > 1, IllegalArgumentException.class, "confidenceLevel should be between 0 and 1 (inclusive)"); synchronized (this.semaphore) { double sampleMean = getSampleMean(); if (Double.isNaN(sampleMean) || Double.valueOf(this.getSampleStDev()).isNaN()) { return null; // TODO throw something } double level = 1 - alpha; if (side.equals(ConfidenceInterval.BOTH_SIDE_CONFIDENCE)) { level = 1 - alpha / 2.0; } double z = DistNormalTable.getInverseCumulativeProbability(0.0, 1.0, level); double confidence = z * Math.sqrt(this.getSampleVariance() / this.n); double[] result = {sampleMean - confidence, sampleMean + confidence}; if (side.equals(ConfidenceInterval.LEFT_SIDE_CONFIDENCE)) { result[1] = sampleMean; } if (side.equals(ConfidenceInterval.RIGHT_SIDE_CONFIDENCE)) { result[0] = sampleMean; } result[0] = Math.max(result[0], this.min); result[1] = Math.min(result[1], this.max); return result; } } /** {@inheritDoc} */ @Override public final String getDescription() { return this.description; } /** {@inheritDoc} */ @Override public final double getMax() { return this.max; } /** {@inheritDoc} */ @Override public final double getMin() { return this.min; } /** {@inheritDoc} */ @Override public final long getN() { return this.n; } /** {@inheritDoc} */ @Override public final double getSampleStDev() { synchronized (this.semaphore) { if (this.n > 1) { return Math.sqrt(getSampleVariance()); } return Double.NaN; } } /** {@inheritDoc} */ @Override public final double getPopulationStDev() { synchronized (this.semaphore) { return Math.sqrt(getPopulationVariance()); } } /** {@inheritDoc} */ @Override public final double getSum() { return this.sum; } /** {@inheritDoc} */ @Override public final double getSampleVariance() { synchronized (this.semaphore) { if (this.n > 1) { return this.m2 / (this.n - 1); } return Double.NaN; } } /** {@inheritDoc} */ @Override public final double getPopulationVariance() { synchronized (this.semaphore) { if (this.n > 0) { return this.m2 / this.n; } return Double.NaN; } } /** {@inheritDoc} */ @Override public final double getSampleSkewness() { if (this.n > 2) { return getPopulationSkewness() * Math.sqrt(this.n * (this.n - 1)) / (this.n - 2); } return Double.NaN; } /** {@inheritDoc} */ @Override public final double getPopulationSkewness() { if (this.n > 1) { return (this.m3 / this.n) / Math.pow(getPopulationVariance(), 1.5); } return Double.NaN; } /** {@inheritDoc} */ @Override public final double getSampleKurtosis() { if (this.n > 3) { double sVar = getSampleVariance(); return this.m4 / (this.n - 1) / sVar / sVar; } return Double.NaN; } /** {@inheritDoc} */ @Override public final double getPopulationKurtosis() { if (this.n > 2) { return (this.m4 / this.n) / (this.m2 / this.n) / (this.m2 / this.n); } return Double.NaN; } /** {@inheritDoc} */ @Override public final double getSampleExcessKurtosis() { if (this.n > 3) { double g2 = getPopulationExcessKurtosis(); return (1.0 * (this.n - 1) / (this.n - 2) / (this.n - 3)) * ((this.n + 1) * g2 + 6.0); } return Double.NaN; } /** {@inheritDoc} */ @Override public final double getPopulationExcessKurtosis() { if (this.n > 2) { // convert kurtosis to excess kurtosis, shift by -3 return getPopulationKurtosis() - 3.0; } return Double.NaN; } /** {@inheritDoc} */ @Override public void initialize() { synchronized (this.semaphore) { this.min = Double.NaN; this.max = Double.NaN; this.n = 0; this.sum = 0.0; this.m1 = 0.0; this.m2 = 0.0; this.m3 = 0; this.m4 = 0; this.quantileAccumulator.initialize(); } } /** * Ingest an array of values. * @param values double...; the values to ingest */ public void ingest(final double... values) { for (double value : values) { ingest(value); } } /** {@inheritDoc} */ @Override public double ingest(final double value) { Throw.when(Double.isNaN(value), IllegalArgumentException.class, "value may not be NaN"); synchronized (this.semaphore) { if (this.n == 0) { this.min = Double.MAX_VALUE; this.max = -Double.MAX_VALUE; } this.n++; double delta = value - this.m1; double oldm2 = this.m2; double oldm3 = this.m3; // Eq 4 in https://fanf2.user.srcf.net/hermes/doc/antiforgery/stats.pdf // Eq 1.1 in https://prod-ng.sandia.gov/techlib-noauth/access-control.cgi/2008/086212.pdf this.m1 += delta / this.n; // Eq 44 in https://fanf2.user.srcf.net/hermes/doc/antiforgery/stats.pdf // Eq 1.2 in https://prod-ng.sandia.gov/techlib-noauth/access-control.cgi/2008/086212.pdf this.m2 += delta * (value - this.m1); // Eq 2.13 in https://prod-ng.sandia.gov/techlib-noauth/access-control.cgi/2008/086212.pdf this.m3 += -3 * oldm2 * delta / this.n + (this.n - 1) * (this.n - 2) * delta * delta * delta / this.n / this.n; // Eq 2.16 in https://prod-ng.sandia.gov/techlib-noauth/access-control.cgi/2008/086212.pdf this.m4 += -4 * oldm3 * delta / this.n + 6 * oldm2 * delta * delta / this.n / this.n + (this.n - 1) * (this.n * this.n - 3 * this.n + 3) * delta * delta * delta * delta / this.n / this.n / this.n; this.sum += value; if (value < this.min) { this.min = value; } if (value > this.max) { this.max = value; } this.quantileAccumulator.ingest(value); } return value; } /** {@inheritDoc} */ @Override @SuppressWarnings("checkstyle:designforextension") public String toString() { return "Tally [sum=" + this.sum + ", m1=" + this.m1 + ", m2=" + this.m2 + ", m3=" + this.m3 + ", m4=" + this.m4 + ", min=" + this.min + ", max=" + this.max + ", n=" + this.n + ", description=" + this.description + ", quantileAccumulator=" + this.quantileAccumulator + "]"; } }