/// <summary> /// Estimates the specified quantile /// </summary> /// <param name="quantile">The quantile to estimate. Must be between 0 and 1.</param> /// <returns>The value for the estimated quantile</returns> public double Quantile(double quantile) { if (quantile < 0 || quantile > 1) { throw new ArgumentOutOfRangeException(nameof(quantile), "must be between 0 and 1"); } if (centroids.Count == 0) { throw new InvalidOperationException( "Cannot call Quantile() method until first Adding values to the digest"); } if (centroids.Count == 1) { return(centroids.First().Value.Mean); } double index = quantile * count; if (index < 1) { return(Min); } if (index > Count - 1) { return(Max); } Centroid currentNode = centroids.First().Value; Centroid lastNode = centroids.Last().Value; double currentWeight = currentNode.Count; if (Math.Abs(currentWeight - 2) < Tolerance && index <= 2) { // first node is a double weight with one sample at min, sou we can infer location of other sample return(2 * currentNode.Mean - Min); } if (Math.Abs(centroids.Last().Value.Count - 2) < Tolerance && index > Count - 2) { // likewise for last centroid return(2 * lastNode.Mean - Max); } double weightSoFar = currentWeight / 2.0; if (index < weightSoFar) { return(WeightedAvg(Min, weightSoFar - index, currentNode.Mean, index - 1)); } foreach (Centroid nextNode in centroids.Values.Skip(1)) { double nextWeight = nextNode.Count; double dw = (currentWeight + nextWeight) / 2.0; if (index < weightSoFar + dw) { double leftExclusion = 0; double rightExclusion = 0; if (Math.Abs(currentWeight - 1) < Tolerance) { if (index < weightSoFar + 0.5) { return(currentNode.Mean); } leftExclusion = 0.5; } if (Math.Abs(nextWeight - 1) < Tolerance) { if (index >= weightSoFar + dw - 0.5) { return(nextNode.Mean); } rightExclusion = 0.5; } // centroids i and i+1 bracket our current point // we interpolate, but the weights are diminished if singletons are present double weight1 = index - weightSoFar - leftExclusion; double weight2 = weightSoFar + dw - index - rightExclusion; return(WeightedAvg(currentNode.Mean, weight2, nextNode.Mean, weight1)); } weightSoFar += dw; currentNode = nextNode; currentWeight = nextWeight; } double w1 = index - weightSoFar; double w2 = Count - 1 - index; return(WeightedAvg(currentNode.Mean, w2, Max, w1)); }