/** * Licensed to Apereo under one or more contributor license agreements. See the NOTICE file * distributed with this work for additional information regarding copyright ownership. Apereo * 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 the * following location: * * <p>http://www.apache.org/licenses/LICENSE-2.0 * * <p>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.apereo.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.apereo.portal.events.aggr.TimedAggregationStatistics; /** * Semi-Clone of {@link SummaryStatistics} that can be persisted in a database * */ @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. * * @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. * * @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. * * @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>Double.NaN is returned if no values have been added. * * @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. * * @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. * * @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. * * @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. * * @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. * * @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> * * @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; } }