/*
* Copyright 2007 Flaptor (flaptor.com)
* Licensed 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 com.flaptor.hist4j;
import java.io.Serializable;
import java.util.ArrayList;
/**
* This class implements a histogram that adapts to an unknown data distribution.
* It keeps a more or less constant resolution throughout the data range by increasing
* the resolution where the data is more dense. For example, if the data has such
* such a distribution that most of the values lie in the 0-5 range and only a few are
* in the 5-10 range, the histogram would adapt and assign more counting buckets to
* the 0-5 range and less to the 5-10 range.
* This implementation provides a method to obtain the accumulative density function
* for a given data point, and a method to obtain the data point that splits the
* data set at a given percentile.
*
* @author Jorge Handl
*/
public class AdaptiveHistogram implements Serializable {
private static final long serialVersionUID = -1L;
private long totalCount; // total number of data points
private HistogramNode root; // root of the tree
/**
* Class constructor.
*/
public AdaptiveHistogram() {
root = null;
reset();
}
/**
* Erases all data from the histogram.
*/
public void reset() {
if (null != root) {
root.reset();
root = null;
}
totalCount = 0;
}
/**
* Adds a data point to the histogram.
*
* @param value
* the data point to add.
*/
public synchronized void addValue(float value) {
totalCount++;
if (null == root) {
root = new HistogramDataNode();
}
root = root.addValue(this, value);
}
/**
* Returns the number of data points stored in the same bucket as a given value.
*
* @param value
* the reference data point.
* @return the number of data points stored in the same bucket as the reference point.
*/
public long getCount(float value) {
long count = 0;
if (null != root) {
count = root.getCount(value);
}
return count;
}
/**
* Returns the cumulative density function for a given data point.
*
* @param value
* the reference data point.
* @return the cumulative density function for the reference point.
*/
public long getAccumCount(float value) {
long count = 0;
if (null != root) {
count = root.getAccumCount(value);
}
return count;
}
/**
* Returns the data point that splits the data set at a given percentile.
*
* @param percentile
* the percentile at which the data set is split.
* @return the data point that splits the data set at the given percentile.
*/
public float getValueForPercentile(int percentile) {
long targetAccumCount = (totalCount * percentile) / 100;
Float value = new Float(0);
if (null != root) {
value = root.getValueForAccumCount(new long[] { 0, targetAccumCount });
}
return (null != value) ? value.floatValue() : 0;
}
/**
* This method is used by the internal data structure of the histogram to get the
* limit of data points that should be counted at one bucket.
*
* @return the limit of data points to store a one bucket.
*/
protected int getCountPerNodeLimit() {
int limit = (int) (totalCount / 10);
if (0 == limit) {
limit = 1;
}
return limit;
}
/**
* Auxiliary interface for inline functor object.
*/
protected interface ValueConversion {
/**
* This method should implement the conversion function.
*
* @param value
* the input value.
* @return the resulting converted value.
*/
float convertValue(float value);
}
/**
* Normalizes all the values to the desired range.
*
* @param targetMin
* the target new minimum value.
* @param targetMax
* the target new maximum value.
*/
public void normalize(float targetMin, float targetMax) {
if (null != root) {
final float min = getValueForPercentile(0);
final float max = getValueForPercentile(100);
final float m = (targetMax - targetMin) * ((max > min) ? 1 / (max - min) : 1);
final float b = targetMin;
root.apply(new ValueConversion() {
public float convertValue(float value) {
return m * (value - min) + b;
}
});
}
}
/**
* Shows the histograms' underlying data structure.
*/
public void show() {
System.out.println("Histogram has " + totalCount + " values:");
if (null != root) {
root.show(0);
}
}
/**
* Return a table representing the data in this histogram.
* Each element is a table cell containing the range limit values and the count for that range.
*/
public ArrayList<Cell> toTable() {
ArrayList<Cell> table = new ArrayList<Cell>();
if (null != root) {
root.toTable(table);
}
return table;
}
}