/*
This file is part of Cyclos (www.cyclos.org).
A project of the Social Trade Organisation (www.socialtrade.org).
Cyclos is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
Cyclos is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Cyclos; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package nl.strohalm.cyclos.entities.reports;
/**
* A convenient way to store the numerical results of the statistics. As the presentation of statistical results may take various forms, such as "3.12
* +/- 1.76", "12" or "34%". This class can handle all these formats. It does not provide a toString method, as the settings with the application's
* NumberFormat are not easily accessible from cyclos3. The rendering in jsp's is taken care of in the FormatTag.java class.
* @author Rinke
*
*/
public class StatisticalNumber extends Number implements Comparable<StatisticalNumber> {
private static final long serialVersionUID = -7277480165783349984L;
/**
* A factory method for the creation of a pValue which could not be created due to the fact that n-values were too small.
* @return a StatisitcalNumber with invalid pvalue.
*/
public static StatisticalNumber createNullPvalue() {
final StatisticalNumber sn = new StatisticalNumber(0);
sn.isNull = true;
sn.pvalue = true;
return sn;
}
/**
* a factory method for the creation of numbers representing percentages.
* @param value a Double representing the percentage. This parameter may be null, meaning that the percentage cannot be calculated (for example in
* case of growth from 0 to 0).
* @return a StatisticalNumber which will be interpreted as a percentage, that is: with percentage set to true, and a precision of 0 digits after
* the decimal point.
*/
public static StatisticalNumber createPercentage(final Double value) {
StatisticalNumber statisticalNumber = null;
if (value == null) {
statisticalNumber = new StatisticalNumber(0);
statisticalNumber.isNull = true;
} else {
statisticalNumber = new StatisticalNumber(value);
}
statisticalNumber.percentage = true;
return statisticalNumber;
}
/**
* a factory method for the creation of numbers representing percentages. Is an overloaded version of the single parameter version. Calculates the
* difference from value2 to value1.<br>
* Example:
*
* <pre>
* if value2 = 500, and value1 = 750, then the result is a
* StatisticalNumber representing 50%, because the growth from 500 to 750 is
* 50%.
* </pre>
*
* @param value1 - a Number representing value1. Accepts null, but then the result is also null.
* @param value2 - a Number representing value2. Accepts null, but then te result is also null.
* @return a StatisticalNumber which will be interpreted as a percentage, that is: with percentage set to true, and a precision of 0 digits after
* the decimal point.
*/
public static StatisticalNumber createPercentage(final Number value1, final Number value2) {
if (value1 instanceof StatisticalNumber) {
final StatisticalNumber sn1 = (StatisticalNumber) value1;
if (sn1.getValue() == null || sn1.isNull) {
return StatisticalNumber.createPercentage(null);
}
}
if (value2 instanceof StatisticalNumber) {
final StatisticalNumber sn2 = (StatisticalNumber) value2;
if (sn2.getValue() == null || sn2.isNull) {
return StatisticalNumber.createPercentage(null);
}
}
if (value1 == null || value2 == null) {
return StatisticalNumber.createPercentage(null);
}
if (value2.floatValue() == 0) {
return StatisticalNumber.createPercentage(null);
} else {
final double percentage = 100 * (value1.floatValue() - value2.floatValue()) / value2.floatValue();
return StatisticalNumber.createPercentage(percentage);
}
}
/**
* A factory method for the creation of numbers representing p-values.
* @param value a double representing the p-value
* @return a StatisticalNumber which will be interpreted as a p-value, that is: with isPvalue set to true, and a precision of 3 digits after the
* decimal point.
*/
public static StatisticalNumber createPvalue(final double value) {
final StatisticalNumber statisticalNumber = new StatisticalNumber(value);
statisticalNumber.pvalue = true;
statisticalNumber.precision = 3;
return statisticalNumber;
}
// //////////////// other methods //////////////////////////
/**
* allows for scaling of any Number, which is (for example) division by 1000.
* @param oldNumber a Number to be scaled. Can take any Number subclass.
* @param scaleFactor a double indicating the number by which the values should be divided, for example 1000
* @return a new scaled Number. If the oldNumber param was...
* <ul>
* <li><b>null</b> then a null is returned.
* <li><b>a StatisticalNumber: </b>
* <ul>
* <li>if indicating a pvalue, then the oldNumber is returned.
* <li>if indicating a percentage, then the oldNumber is returned.
* <li>if indicating another StatisticalNumber, then value and error are scaled, but precision is the same.
* </ul>
* <li><b>any other Number</b> then a scaled Double is returned.
* </ul>
*/
public static Number scale(final Number oldNumber, final double scaleFactor) {
if (oldNumber == null) {
return null;
}
Number newNumber;
if (oldNumber instanceof StatisticalNumber) {
final StatisticalNumber statisticalOldNumber = (StatisticalNumber) oldNumber;
if (statisticalOldNumber.isNull) {
return new StatisticalNumber();
}
if (statisticalOldNumber.pvalue | statisticalOldNumber.percentage) {
return oldNumber;
}
final double newValue = statisticalOldNumber.doubleValue() / scaleFactor;
if (statisticalOldNumber.error == null && statisticalOldNumber.lowerBound != null) {
final Double newLower = statisticalOldNumber.lowerBound / scaleFactor;
final Double newUpper = statisticalOldNumber.upperBound / scaleFactor;
newNumber = new StatisticalNumber(newValue, newLower, newUpper, statisticalOldNumber.precision);
} else {
final Double newError = (statisticalOldNumber.error == null) ? null : statisticalOldNumber.error / scaleFactor;
newNumber = new StatisticalNumber(newValue, newError, statisticalOldNumber.precision);
}
} else {
newNumber = oldNumber.doubleValue() / scaleFactor;
}
return newNumber;
}
/**
* a Double storing the value of the number.
*/
private final Double value;
/**
* a double storing the error.
*/
private Double error;
/**
* a byte indicating the precision, that is: the number of digits after the decimal point or comma.
*/
private byte precision;
// ///////////// CONSTRUCTORS and semi-constructors /////////////////////////////
/**
* the lower limit of the confidence interval. So value - error = lowerbound
*/
private Double lowerBound;
/**
* the upper limit of the confidence interval. So value + error = upperbound
*/
private Double upperBound;
/**
* true if the number represents a p-value.
*/
private boolean pvalue;
/**
* true if the number represents a percentage.
*/
private boolean percentage;
/**
* true if a percentage or p-value connot be calculated
*/
private boolean isNull;
/**
* null constructor, creates a null value
*/
public StatisticalNumber() {
value = null;
isNull = true;
}
/**
* Most simple constructor, creating numbers presented as integers. To be used for the most simple form, a single int like "12".
*
* @param value a double which is the value to be assigned to the object
*/
public StatisticalNumber(final double value) {
this.value = new Double(value);
}
/**
* Constructor for simple decimal numbers like "6.234".
* @param value a double being the value of the object.
* @param precision the number of digits after the decimal point.
*/
public StatisticalNumber(final double value, final byte precision) {
this.value = new Double(value);
this.precision = precision;
}
/**
* Constructor for a StatisticalNumber with a value and an error indication. This constructor must be used for numbers taking the form "12.12 �
* 2.76"
* @param value a double indicating the value
* @param error a Double indicating the error, so the number after the � sign. This is the only param which can take the value null.
* @param precision a byte indicating the number of digits after the decimal point
*/
public StatisticalNumber(final double value, final Double error, final byte precision) {
this.value = new Double(value);
this.error = error;
this.precision = precision;
}
/**
* Constructor for a StatisticalNumber with a value and error indication, specifically designed for <b>a-symmetrical</b> error bars. In this
* case, the error field will be left empty, and only the lowerBound and upperBound fields will be set. The number will be printed as "12.12 (10.0 -
* 17.2)". For this type of StatisticalNumber, hasErrorBars() returns true, but hasSymmetricalErrorBars returns false
*
* @param value a double indicating the value
* @param lowerBound a Double indicating the lower value of the error range
* @param upperBound a Double indicating the upper value of the error range
* @param precision a byte indicating the number of digits after the decimal point.
*/
public StatisticalNumber(final double value, final Double lowerBound, final Double upperBound, final byte precision) {
if (lowerBound == null || upperBound == null) {
throw new IllegalArgumentException("lower and upper Bounds may not be null with this constructor");
}
this.value = new Double(value);
this.lowerBound = lowerBound;
this.upperBound = upperBound;
this.precision = precision;
}
/**
* special null allowing constructor
*
* @param nullAllowedValue as a Double
*/
public StatisticalNumber(final Double nullAllowedValue) {
value = nullAllowedValue;
isNull = value == null;
}
/**
* special null allowing constructor
*
* @param nullAllowedValue as an Integer
*/
public StatisticalNumber(final Integer nullAllowedValue) {
if (nullAllowedValue == null) {
value = null;
isNull = true;
} else {
value = new Double(nullAllowedValue.intValue());
}
}
/**
* implementation of the standard compareTo method of the Comparable interface.
*
* @param o the object to compare to
* @return -1, 0 or 1 if this object is smaller, equal to, or greater than the param.
*/
public int compareTo(final StatisticalNumber o) {
if (this == o) {
return 0;
}
if ((o == null)) {
return -1;
}
return (int) (doubleValue() - o.doubleValue());
}
// ////////////// XValues /////////////////////////////
/**
* gets the doubleValue of value
*/
@Override
public double doubleValue() {
return value.doubleValue();
}
/**
* gets the floatValue of value
*/
@Override
public float floatValue() {
return value.floatValue();
}
/**
* gets the error
*/
public Double getError() {
return error;
}
/**
* gets the Lowest value of the error bar. Returns null if no error is defined.
* @return lower limit of the error bar
*/
public Double getLowerBound() {
if (lowerBound == null) {
return (error == null) ? null : (value - error);
}
return lowerBound;
}
/**
* gets the precision
*/
public byte getPrecision() {
return precision;
}
// ////////////////// getters / setters /////////////////////
/**
* gets the highest value of the error bar. Returns null if no error is defined
* @return upper limit of the error bar.
*/
public Double getUpperBound() {
if (upperBound == null) {
return (error == null) ? null : (value + error);
}
return upperBound;
}
/**
* gets the value
*/
public Double getValue() {
return value;
}
/**
* tests if the statisticalNumber has data. By definition it does not have data when:
* <ul>
* <li> it is marked as <code>null</code>
* <li> its value is <code>null</code>
* <li> its value is 0
* </ul>
*
* @return <code>false</code> if it does not have data according to the above definition, <code>true</code> otherwise.
*/
public boolean hasEnoughData() {
if (!isNull() && getValue() != null && getValue().doubleValue() != 0) {
return true;
}
return false;
}
/**
* @return true if a confidence interval is defined for this number.
*/
public boolean hasErrorBar() {
return (getLowerBound() != null);
}
/**
* @return true if a confidence interval is defined, and if this confidence interval is symmetrical. If no errorBar is defined, it returns false.
*/
public boolean hasSymmetricalErrorBar() {
if (hasErrorBar()) {
return (getError() != null);
}
return false;
}
/**
* gets the intValue of value
*/
@Override
public int intValue() {
return value.intValue();
}
/**
* checks if the value cannot be calculated (used in case a percentage cannot be calculated, because the nominator is 0).
* @return true if the value cannot be calculated
*/
public boolean isNull() {
return isNull;
}
/**
* checks if the object represents a percentage
* @return a boolean indicating if this represents a percentage
*/
public boolean isPercentage() {
return percentage;
}
/**
* checks if the object represents a p-value
* @return a boolean indicating if this represents a p-value
*/
public boolean isPvalue() {
return pvalue;
}
/**
* gets the longValue of value
*/
@Override
public long longValue() {
return value.longValue();
}
/**
* sets the precision
* @param precision
*/
public void setPrecision(final byte precision) {
this.precision = precision;
}
}