/**
* Licensed to Jasig under one or more contributor license
* agreements. See the NOTICE file distributed with this work
* for additional information regarding copyright ownership.
* Jasig 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.jasig.portal.events.aggr.stat;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.Transient;
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
import org.apache.commons.math3.stat.descriptive.moment.GeometricMean;
import org.apache.commons.math3.stat.descriptive.moment.Mean;
import org.apache.commons.math3.stat.descriptive.moment.SecondMoment;
import org.apache.commons.math3.stat.descriptive.moment.Variance;
import org.apache.commons.math3.stat.descriptive.rank.Max;
import org.apache.commons.math3.stat.descriptive.rank.Min;
import org.apache.commons.math3.stat.descriptive.summary.Sum;
import org.apache.commons.math3.stat.descriptive.summary.SumOfLogs;
import org.apache.commons.math3.stat.descriptive.summary.SumOfSquares;
import org.apache.commons.math3.util.FastMath;
import org.apache.commons.math3.util.MathUtils;
import org.apache.commons.math3.util.Precision;
import org.jasig.portal.events.aggr.TimedAggregationStatistics;
/**
* Semi-Clone of {@link SummaryStatistics} that can be persisted in a database
*
* @author Eric Dalquist
*/
@Embeddable
public class JpaStatisticalSummary implements TimedAggregationStatistics {
/** SecondMoment is used to compute the mean and variance */
@Embedded
private SecondMoment secondMoment;
/** sum of values that have been added */
@Embedded
private Sum sum;
/** sum of the square of each value that has been added */
@Embedded
private SumOfSquares sumsq;
/** min of values that have been added */
@Embedded
private Min min;
/** max of values that have been added */
@Embedded
private Max max;
/** sumLog of values that have been added */
@Embedded
private SumOfLogs sumLog;
/** geoMean of values that have been added */
@Transient
private GeometricMean geoMean;
/** mean of values that have been added */
@Transient
private Mean mean;
/** variance of values that have been added */
@Transient
private Variance variance;
//***** ALL FIELDS ARE LAZILY INITIALIZED HERE *****//
private SecondMoment _getSecondMoment() {
if (this.secondMoment == null) {
this.secondMoment = new SecondMoment();
}
return this.secondMoment;
}
private Sum _getSum() {
if (this.sum == null) {
this.sum = new Sum();
}
return this.sum;
}
private SumOfSquares _getSumsq() {
if (this.sumsq == null) {
this.sumsq = new SumOfSquares();
}
return this.sumsq;
}
private Min _getMin() {
if (this.min == null) {
this.min = new Min();
}
return this.min;
}
private Max _getMax() {
if (this.max == null) {
this.max = new Max();
}
return this.max;
}
private SumOfLogs _getSumLog() {
if (this.sumLog == null) {
this.sumLog = new SumOfLogs();
}
return this.sumLog;
}
private GeometricMean _getGeoMean() {
if (this.geoMean == null) {
this.geoMean = new GeometricMean(this._getSumLog());
}
return this.geoMean;
}
private Mean _getMean() {
if (this.mean == null) {
this.mean = new Mean(this._getSecondMoment());
}
return this.mean;
}
private Variance _getVariance() {
if (this.variance == null) {
this.variance = new Variance(this._getSecondMoment());
}
return this.variance;
}
public void addValue(double value) {
_getSum().increment(value);
_getSumsq().increment(value);
_getMin().increment(value);
_getMax().increment(value);
_getSumLog().increment(value);
_getSecondMoment().increment(value);
}
/**
* Returns the number of available values
* @return The number of available values
*/
public long getN() {
return _getSum().getN();
}
/**
* Returns the sum of the values that have been added
* @return The sum or <code>Double.NaN</code> if no values have been added
*/
public double getSum() {
return _getSum().getResult();
}
/**
* Returns the sum of the squares of the values that have been added.
* <p>
* Double.NaN is returned if no values have been added.
* </p>
* @return The sum of squares
*/
public double getSumsq() {
return _getSumsq().getResult();
}
/**
* Returns the mean of the values that have been added.
* <p>
* Double.NaN is returned if no values have been added.
* </p>
* @return the mean
*/
public double getMean() {
return _getMean().getResult();
}
/**
* Returns the standard deviation of the values that have been added.
* <p>
* Double.NaN is returned if no values have been added.
* </p>
* @return the standard deviation
*/
public double getStandardDeviation() {
double stdDev = Double.NaN;
if (getN() > 0) {
if (getN() > 1) {
stdDev = FastMath.sqrt(getVariance());
} else {
stdDev = 0.0;
}
}
return stdDev;
}
/**
* Returns the (sample) variance of the available values.
*
* <p>This method returns the bias-corrected sample variance (using {@code n - 1} in
* the denominator). Use {@link #getPopulationVariance()} for the non-bias-corrected
* population variance.</p>
*
* <p>Double.NaN is returned if no values have been added.</p>
*
* @return the variance
*/
public double getVariance() {
return _getVariance().getResult();
}
/**
* Returns the <a href="http://en.wikibooks.org/wiki/Statistics/Summary/Variance">
* population variance</a> of the values that have been added.
*
* <p>Double.NaN is returned if no values have been added.</p>
*
* @return the population variance
*/
public double getPopulationVariance() {
Variance populationVariance = new Variance(_getSecondMoment());
populationVariance.setBiasCorrected(false);
return populationVariance.getResult();
}
/**
* Returns the maximum of the values that have been added.
* <p>
* Double.NaN is returned if no values have been added.
* </p>
* @return the maximum
*/
public double getMax() {
return _getMax().getResult();
}
/**
* Returns the minimum of the values that have been added.
* <p>
* Double.NaN is returned if no values have been added.
* </p>
* @return the minimum
*/
public double getMin() {
return _getMin().getResult();
}
/**
* Returns the geometric mean of the values that have been added.
* <p>
* Double.NaN is returned if no values have been added.
* </p>
* @return the geometric mean
*/
public double getGeometricMean() {
return _getGeoMean().getResult();
}
/**
* Returns the sum of the logs of the values that have been added.
* <p>
* Double.NaN is returned if no values have been added.
* </p>
* @return the sum of logs
* @since 1.2
*/
public double getSumOfLogs() {
return _getSumLog().getResult();
}
/**
* Returns a statistic related to the Second Central Moment. Specifically,
* what is returned is the sum of squared deviations from the sample mean
* among the values that have been added.
* <p>
* Returns <code>Double.NaN</code> if no data values have been added and
* returns <code>0</code> if there is just one value in the data set.</p>
* <p>
* @return second central moment statistic
* @since 2.0
*/
public double getSecondMoment() {
return _getSecondMoment().getResult();
}
/**
* Generates a text report displaying summary statistics from values that
* have been added.
* @return String with line feeds displaying statistics
* @since 1.2
*/
@Override
public String toString() {
StringBuilder outBuffer = new StringBuilder();
outBuffer.append("SummaryStatistics:").append("\n");
outBuffer.append("n: ").append(getN()).append("\n");
outBuffer.append("min: ").append(getMin()).append("\n");
outBuffer.append("max: ").append(getMax()).append("\n");
outBuffer.append("mean: ").append(getMean()).append("\n");
outBuffer.append("geometric mean: ").append(getGeometricMean()).append("\n");
outBuffer.append("variance: ").append(getVariance()).append("\n");
outBuffer.append("sum of squares: ").append(getSumsq()).append("\n");
outBuffer.append("standard deviation: ").append(getStandardDeviation()).append("\n");
outBuffer.append("sum of logs: ").append(getSumOfLogs()).append("\n");
return outBuffer.toString();
}
/**
* Returns true iff <code>object</code> is a
* <code>SummaryStatistics</code> instance and all statistics have the
* same values as this.
* @param object the object to test equality against.
* @return true if object equals this
*/
@Override
public boolean equals(Object object) {
if (object == this) {
return true;
}
if (object instanceof SummaryStatistics == false) {
return false;
}
SummaryStatistics stat = (SummaryStatistics)object;
return Precision.equalsIncludingNaN(stat.getGeometricMean(), getGeometricMean()) &&
Precision.equalsIncludingNaN(stat.getMax(), getMax()) &&
Precision.equalsIncludingNaN(stat.getMean(), getMean()) &&
Precision.equalsIncludingNaN(stat.getMin(), getMin()) &&
Precision.equalsIncludingNaN(stat.getN(), getN()) &&
Precision.equalsIncludingNaN(stat.getSum(), getSum()) &&
Precision.equalsIncludingNaN(stat.getSumsq(), getSumsq()) &&
Precision.equalsIncludingNaN(stat.getVariance(), getVariance());
}
/**
* Returns hash code based on values of statistics
* @return hash code
*/
@Override
public int hashCode() {
int result = 31 + MathUtils.hash(getGeometricMean());
result = result * 31 + MathUtils.hash(getGeometricMean());
result = result * 31 + MathUtils.hash(getMax());
result = result * 31 + MathUtils.hash(getMean());
result = result * 31 + MathUtils.hash(getMin());
result = result * 31 + MathUtils.hash(getN());
result = result * 31 + MathUtils.hash(getSum());
result = result * 31 + MathUtils.hash(getSumsq());
result = result * 31 + MathUtils.hash(getVariance());
return result;
}
}