/*
* The MIT License (MIT)
*
* Copyright (c) 2015 Lachlan Dowding
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package permafrost.tundra.math;
/**
* Class for incrementally calculating the mean and standard deviation.
*/
public class NormalDistributionEstimator {
protected long count;
protected double mean, sq, minimum, maximum;
protected String unit = "";
/**
* Constructs a new estimator object.
*/
public NormalDistributionEstimator() {
reset();
}
/**
* Constructs a new estimator object.
*
* @param unit The unit of measurement related to the measured samples.
*/
public NormalDistributionEstimator(String unit) {
this();
this.unit = unit;
}
/**
* Constructs a new estimator object seeded with the given samples.
*
* @param samples One or more initial samples to seed the estimator with.
*/
public NormalDistributionEstimator(double... samples) {
this();
add(samples);
}
/**
* Constructs a new estimator object seeded with the given samples.
*
* @param unit The unit of measurement related to the measured samples.
* @param samples One or more initial samples to seed the estimator with.
*/
public NormalDistributionEstimator(String unit, double... samples) {
this(unit);
add(samples);
}
/**
* Constructs a new estimator object seeded with the given collection of samples.
*
* @param samples An initial collection of samples to seed the estimator with.
*/
public NormalDistributionEstimator(java.util.Collection<Double> samples) {
this();
add(samples);
}
/**
* Constructs a new estimator object seeded with the given collection of samples.
*
* @param unit The unit of measurement related to the measured samples.
* @param samples An initial collection of samples to seed the estimator with.
*/
public NormalDistributionEstimator(String unit, java.util.Collection<Double> samples) {
this(unit);
add(samples);
}
/**
* Adds a new sample to the set of samples used for estimating the standard deviation.
*
* @param sample The sample to be added.
* @return The estimator object itself, to support method chaining.
*/
public synchronized final NormalDistributionEstimator add(double sample) {
count++;
if (count == 1) {
mean = minimum = maximum = sample;
sq = 0.0;
} else {
double previousMean = mean;
mean = previousMean + ((sample - previousMean) / count);
sq = sq + (sample - previousMean) * (sample - mean);
if (minimum > sample) minimum = sample;
if (maximum < sample) maximum = sample;
}
return this;
}
/**
* Adds one or more samples to the set of samples used for estimating the standard deviation.
*
* @param samples One or more samples to be added.
* @return The estimator object itself, to support method chaining.
*/
public final NormalDistributionEstimator add(double... samples) {
for (double sample : samples) {
add(sample);
}
return this;
}
/**
* Adds a collection of samples to the set of samples used for estimating the standard deviation.
*
* @param samples A collection of samples to be added.
* @return The estimator object itself, to support method chaining.
*/
public final NormalDistributionEstimator add(java.util.Collection<Double> samples) {
for (double sample : samples) {
add(sample);
}
return this;
}
/**
* Resets the estimator back to a set of zero samples.
*
* @return The Estimator object itself, to support method chaining.
*/
public synchronized NormalDistributionEstimator reset() {
count = 0;
mean = 0.0;
sq = 0.0;
minimum = 0.0;
maximum = 0.0;
return this;
}
/**
* Returns the number of samples seen by this estimator.
*
* @return The number of samples seen by this estimator.
*/
public synchronized long getCount() { return count; }
/**
* Returns the mean of the samples.
*
* @return The mean of the samples.
*/
public synchronized double getMean() { return mean; }
/**
* Returns the minimum of the samples.
*
* @return The minimum of the samples.
*/
public synchronized double getMinimum() {
if (count == 0) throw new IllegalStateException("minimum value is not available when sample count is zero");
return minimum;
}
/**
* Returns the maximum of the samples.
*
* @return The maximum of the samples.
*/
public synchronized double getMaximum() {
if (count == 0) throw new IllegalStateException("maximum value is not available when sample count is zero");
return maximum;
}
/**
* Returns the variance estimate for the current set of samples.
*
* @return The variance estimate.
*/
public synchronized double getVariance() {
return count > 1 ? sq / (count - 1) : 0.0;
}
/**
* Returns the standard deviation estimate for the current set of samples.
*
* @return The standard deviation estimate.
*/
public double getStandardDeviation() {
return Math.sqrt(getVariance());
}
/**
* Returns the unit of measure related to the measured samples.
*
* @return The unit of measure.
*/
public String getUnit() {
return unit;
}
/**
* Returns a string-based representation of the mean, standard deviation and number of samples for this estimator.
*
* @return String-based representation of this estimator.
*/
@Override
public String toString() {
double mean, stdev, min, max;
long count;
String unit = getUnit();
// thread synchronized to make sure all values are consistent
synchronized (this) {
mean = getMean();
stdev = getStandardDeviation();
min = getMinimum();
max = getMaximum();
count = getCount();
}
String output;
if (unit == null || unit.equals("")) {
output = String.format("average = %.3f, standard deviation = %.3f, minimum = %.3f, maximum = %.3f, sample count = %d", mean, stdev, min, max, count);
} else {
output = String.format("average = %.3f %s, standard deviation = %.3f %s, minimum = %.3f %s, maximum = %.3f %s, sample count = %d", mean, unit, stdev, unit, min, unit, max, unit, count);
}
return output;
}
}