/// <summary> /// returns quartiles (0th, 1st, 2nd, 3rd, 4th, 5th.) /// /// see: https://en.wikipedia.org/wiki/Quantile#Examples /// /// This generic overload returns discrete values: the items at those quartile indicies rounded down. (no averaging of values) /// the float based overload will average results. /// </summary> static public Quartiles <T> ComputeQuartiles <T>(Span <T> afVal, int length) { int iSize = length; int iMid = iSize / 2; //this is the mid from a zero based index, eg mid of 7 = 3; var toReturn = new Quartiles <T>(); toReturn.count = length; //q0 and q4 toReturn.q0 = afVal[0]; toReturn.q4 = afVal[length - 1]; toReturn.q2 = afVal[iMid]; int iMidMid = iMid / 2; toReturn.q1 = afVal[iMidMid]; toReturn.q3 = afVal[iMid + iMidMid]; return(toReturn); }
/// <summary> /// returns quartiles (0th, 1st, 2nd, 3rd, 4th, 5th.) /// /// see: https://en.wikipedia.org/wiki/Quantile#Examples /// /// Return the quartile values of an ordered set of doubles /// assume the sorting has already been done. /// /// This actually turns out to be a bit of a PITA, because there is no universal agreement /// on choosing the quartile values. In the case of odd values, some count the median value /// in finding the 1st and 3rd quartile and some discard the median value. /// the two different methods result in two different answers. /// The below method produces the arithmatic mean of the two methods, and insures the median /// is given it's correct weight so that the median changes as smoothly as possible as /// more data ppints are added. /// /// This method uses the following logic: /// /// ===If there are an even number of data points: /// Use the median to divide the ordered data set into two halves. /// The lower quartile value is the median of the lower half of the data. /// The upper quartile value is the median of the upper half of the data. /// /// ===If there are (4n+1) data points: /// The lower quartile is 25% of the nth data value plus 75% of the (n+1)th data value. /// The upper quartile is 75% of the (3n+1)th data point plus 25% of the (3n+2)th data point. /// ///===If there are (4n+3) data points: /// The lower quartile is 75% of the (n+1)th data value plus 25% of the (n+2)th data value. /// The upper quartile is 25% of the (3n+2)th data point plus 75% of the (3n+3)th data point. /// /// </summary> static public Quartiles <float> ComputeQuartiles(Span <float> afVal, int length) { int iSize = length; int iMid = iSize / 2; //this is the mid from a zero based index, eg mid of 7 = 3; var toReturn = new Quartiles <float>(); toReturn.count = length; //q0 and q4 toReturn.q0 = afVal[0]; toReturn.q4 = afVal[length - 1]; if (iSize % 2 == 0) { //================ EVEN NUMBER OF POINTS: ===================== //even between low and high point toReturn.q2 = (afVal[iMid - 1] + afVal[iMid]) / 2; int iMidMid = iMid / 2; //easy split if (iMid % 2 == 0) { toReturn.q1 = (afVal[iMidMid - 1] + afVal[iMidMid]) / 2; toReturn.q3 = (afVal[iMid + iMidMid - 1] + afVal[iMid + iMidMid]) / 2; } else { toReturn.q1 = afVal[iMidMid]; toReturn.q3 = afVal[iMidMid + iMid]; } } else if (iSize == 1) { //================= special case, sorry ================ toReturn.q1 = afVal[0]; toReturn.q2 = afVal[0]; toReturn.q3 = afVal[0]; } else { //odd number so the median is just the midpoint in the array. toReturn.q2 = afVal[iMid]; if ((iSize - 1) % 4 == 0) { //======================(4n-1) POINTS ========================= int n = (iSize - 1) / 4; toReturn.q1 = (afVal[n - 1] * .25f) + (afVal[n] * .75f); toReturn.q3 = (afVal[3 * n] * .75f) + (afVal[3 * n + 1] * .25f); } else if ((iSize - 3) % 4 == 0) { //======================(4n-3) POINTS ========================= int n = (iSize - 3) / 4; toReturn.q1 = (afVal[n] * .75f) + (afVal[n + 1] * .25f); toReturn.q3 = (afVal[3 * n + 1] * .25f) + (afVal[3 * n + 2] * .75f); } } return(toReturn); }
protected override string _QuartileToString(Quartiles <TimeSpan> hist) { return($"{name} Quantiles: {hist.q0.TotalMilliseconds.ToString("F1") } / {hist.q1.TotalMilliseconds.ToString("F1") } / {hist.q2.TotalMilliseconds.ToString("F1") } / {hist.q3.TotalMilliseconds.ToString("F1") } / {hist.q4.TotalMilliseconds.ToString("F1") } ({hist.count} samples)"); }