/** * Copyright (C) 2014-2017 Xavier Witdouck * * 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.zavtech.morpheus.stats; import java.util.Arrays; import org.apache.commons.math3.stat.descriptive.rank.Median; import org.apache.commons.math3.stat.ranking.NaNStrategy; /** * A Statistic implementation that supports incremental calculation of a sample percentile value * * <p><strong>This is open source software released under the <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache 2.0 License</a></strong></p> * * @author Xavier Witdouck */ public class Percentile implements Statistic1 { private int n; private double nth; private double[] values; /** * Constructor * @param nth the requested percentile */ public Percentile(double nth) { this.nth = nth; this.values = new double[1000]; } @Override public long getN() { return n; } @Override public double getValue() { return new org.apache.commons.math3.stat.descriptive.rank.Percentile(nth * 100) .withEstimationType(org.apache.commons.math3.stat.descriptive.rank.Percentile.EstimationType.R_7) .withNaNStrategy(NaNStrategy.FIXED) .evaluate(values, 0, n); } @Override public StatType getType() { return StatType.PERCENTILE; } @Override public long add(double value) { if (!Double.isNaN(value)) { if (++n > values.length) { final int oldCapacity = values.length; final int newCapacity = oldCapacity + (oldCapacity >> 1); final double[] newValues = new double[newCapacity]; System.arraycopy(values, 0, newValues, 0, values.length); this.values = newValues; this.values[n-1] = value; } else { this.values[n-1] = value; } } return n; } @Override public Statistic1 copy() { try { final Percentile clone = (Percentile)super.clone(); clone.values = values.clone(); return clone; } catch (CloneNotSupportedException ex) { throw new RuntimeException("Failed to clone statistic", ex); } } @Override public Statistic1 reset() { this.n = 0; this.values = new double[values.length]; Arrays.fill(values, Double.NaN); return this; } public static void main(String[] args) { final double[] values = new java.util.Random().doubles(5000).toArray(); final Percentile stat1 = new Percentile(0.5); final Median stat2 = new Median(); for (double value : values) stat1.add(value); final double result1 = stat1.getValue(); final double result2 = stat2.evaluate(values); if (result1 != result2) { throw new RuntimeException("Error: " + result1 + " != " + result2); } } }