2013-09-20 22:35:59 +02:00
|
|
|
package com.comphenix.protocol.timing;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents an online algortihm for computing the mean and standard deviation without storing every value.
|
2022-03-08 04:09:04 +01:00
|
|
|
*
|
2013-09-20 22:35:59 +02:00
|
|
|
* @author Kristian
|
|
|
|
*/
|
2014-03-29 23:29:25 +01:00
|
|
|
public class StatisticsStream extends OnlineComputation {
|
2023-05-12 16:35:34 +02:00
|
|
|
// This algorithm is due to Donald Knuth, as described in:
|
|
|
|
// Donald E. Knuth (1998). The Art of Computer Programming, volume 2:
|
|
|
|
// Seminumerical Algorithms, 3rd edn., p. 232. Boston: Addison-Wesley.
|
|
|
|
|
|
|
|
private int count = 0;
|
|
|
|
private double mean = 0;
|
|
|
|
private double m2 = 0;
|
|
|
|
|
|
|
|
// Also keep track of minimum and maximum observation
|
|
|
|
private double minimum = Double.MAX_VALUE;
|
|
|
|
private double maximum = 0;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct a new stream with no observations.
|
|
|
|
*/
|
|
|
|
public StatisticsStream() {
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Construct a copy of the given stream.
|
|
|
|
*
|
|
|
|
* @param other - copy of the stream.
|
|
|
|
*/
|
|
|
|
public StatisticsStream(StatisticsStream other) {
|
|
|
|
this.count = other.count;
|
|
|
|
this.mean = other.mean;
|
|
|
|
this.m2 = other.m2;
|
|
|
|
this.minimum = other.minimum;
|
|
|
|
this.maximum = other.maximum;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public StatisticsStream copy() {
|
|
|
|
return new StatisticsStream(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Observe a value.
|
|
|
|
*
|
|
|
|
* @param value - the observed value.
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public void observe(double value) {
|
|
|
|
double delta = value - this.mean;
|
|
|
|
|
|
|
|
// As per Knuth
|
|
|
|
this.count++;
|
|
|
|
this.mean += delta / this.count;
|
|
|
|
this.m2 += delta * (value - this.mean);
|
|
|
|
|
|
|
|
// Update extremes
|
|
|
|
if (value < this.minimum) {
|
|
|
|
this.minimum = value;
|
|
|
|
}
|
|
|
|
if (value > this.maximum) {
|
|
|
|
this.maximum = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the average of all the observations.
|
|
|
|
*
|
|
|
|
* @return The average.
|
|
|
|
*/
|
|
|
|
public double getMean() {
|
|
|
|
this.checkCount();
|
|
|
|
return this.mean;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the variance of all the observations.
|
|
|
|
*
|
|
|
|
* @return The variance.
|
|
|
|
*/
|
|
|
|
public double getVariance() {
|
|
|
|
this.checkCount();
|
|
|
|
return this.m2 / (this.count - 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the standard deviation of all the observations.
|
|
|
|
*
|
|
|
|
* @return The STDV.
|
|
|
|
*/
|
|
|
|
public double getStandardDeviation() {
|
|
|
|
return Math.sqrt(this.getVariance());
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the minimum observation yet observed.
|
|
|
|
*
|
|
|
|
* @return The minimum observation.
|
|
|
|
*/
|
|
|
|
public double getMinimum() {
|
|
|
|
this.checkCount();
|
|
|
|
return this.minimum;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the maximum observation yet observed.
|
|
|
|
*
|
|
|
|
* @return The maximum observation.
|
|
|
|
*/
|
|
|
|
public double getMaximum() {
|
|
|
|
this.checkCount();
|
|
|
|
return this.maximum;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Combine the two statistics.
|
|
|
|
*
|
|
|
|
* @param other - the other statistics.
|
|
|
|
* @return Combined statistics
|
|
|
|
*/
|
|
|
|
public StatisticsStream add(StatisticsStream other) {
|
|
|
|
// Special cases
|
|
|
|
if (this.count == 0) {
|
|
|
|
return other;
|
|
|
|
} else if (other.count == 0) {
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
StatisticsStream stream = new StatisticsStream();
|
|
|
|
double delta = other.mean - this.mean;
|
|
|
|
double n = this.count + other.count;
|
|
|
|
|
|
|
|
stream.count = (int) n;
|
|
|
|
stream.mean = this.mean + delta * (other.count / n);
|
|
|
|
stream.m2 = this.m2 + other.m2 + ((delta * delta) * (this.count * other.count) / n);
|
|
|
|
stream.minimum = Math.min(this.minimum, other.minimum);
|
|
|
|
stream.maximum = Math.max(this.maximum, other.maximum);
|
|
|
|
return stream;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Retrieve the number of observations.
|
|
|
|
*
|
|
|
|
* @return Number of observations.
|
|
|
|
*/
|
|
|
|
@Override
|
|
|
|
public int getCount() {
|
|
|
|
return this.count;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void checkCount() {
|
|
|
|
if (this.count == 0) {
|
|
|
|
throw new IllegalStateException("No observations in stream.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String toString() {
|
|
|
|
if (this.count == 0) {
|
|
|
|
return "StatisticsStream [Nothing recorded]";
|
|
|
|
}
|
|
|
|
|
|
|
|
return String.format("StatisticsStream [Average: %.3f, SD: %.3f, Min: %.3f, Max: %.3f, Count: %s]",
|
|
|
|
this.getMean(), this.getStandardDeviation(),
|
|
|
|
this.getMinimum(), this.getMaximum(), this.getCount());
|
|
|
|
}
|
2016-05-21 23:37:13 +02:00
|
|
|
}
|