Finding the First and Third Quartiles
I just ran into the same issue, and checking the wikipedia entry for Quartile, it's a bit more complex than it first appears.
My approach was as follows: (which seems to work pretty well for all cases, N=1 on up)...
/// <summary>
/// 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>
internal Tuple<double, double, double> Quartiles(double[] afVal)
{
int iSize = afVal.Length;
int iMid = iSize / 2; //this is the mid from a zero based index, eg mid of 7 = 3;
double fQ1 = 0;
double fQ2 = 0;
double fQ3 = 0;
if (iSize % 2 == 0)
{
//================ EVEN NUMBER OF POINTS: =====================
//even between low and high point
fQ2 = (afVal[iMid - 1] + afVal[iMid]) / 2;
int iMidMid = iMid / 2;
//easy split
if (iMid % 2 == 0)
{
fQ1 = (afVal[iMidMid - 1] + afVal[iMidMid]) / 2;
fQ3 = (afVal[iMid + iMidMid - 1] + afVal[iMid + iMidMid]) / 2;
}
else
{
fQ1 = afVal[iMidMid];
fQ3 = afVal[iMidMid + iMid];
}
}
else if (iSize == 1)
{
//================= special case, sorry ================
fQ1 = afVal[0];
fQ2 = afVal[0];
fQ3 = afVal[0];
}
else
{
//odd number so the median is just the midpoint in the array.
fQ2 = afVal[iMid];
if ((iSize - 1) % 4 == 0)
{
//======================(4n-1) POINTS =========================
int n = (iSize - 1) / 4;
fQ1 = (afVal[n - 1] * .25) + (afVal[n] * .75);
fQ3 = (afVal[3 * n] * .75) + (afVal[3 * n + 1] * .25);
}
else if ((iSize - 3) % 4 == 0)
{
//======================(4n-3) POINTS =========================
int n = (iSize - 3) / 4;
fQ1 = (afVal[n] * .75) + (afVal[n + 1] * .25);
fQ3 = (afVal[3 * n + 1] * .25) + (afVal[3 * n + 2] * .75);
}
}
return new Tuple<double, double, double>(fQ1, fQ2, fQ3);
}
THERE ARE MANY WAYS TO CALCULATE QUARTILES:
I did my best here to implement the version of Quartiles as described as type = 8 Quartile(array, type=8)
in the R documentation: https://www.rdocumentation.org/packages/stats/versions/3.5.1/topics/quantile. This method, is preferred by the authors of the R function, described here, as it produces a smoother transition between values. However, R defaults to method 7, which is the same function used by S and Excel.
If you are just Googling for answers and not thinking about what the output means, or what result you are trying to achieve, this might give you a surprise.
Run the same metod on the following lists:
list1 = list.Where(x => x < Median)
list2 = list.Where(x => x > Median)
Find_Median(list1)
will return first Quartile,
Find_Median(list2)
will return third Quartile
I know this is an old question so I debated whether I should add below answer or not for a little while and since the most voted answer didn't match the numbers with Excel Quartile, I have decided to post below answer.
I also needed to find first and third quartile as I am trying to draw histogram and to create bin width and ranges I am using Freedman–Diaconis rule which requires to know First and Third quartile. I started with Mike's answer.
But during data verification I noticed that result didn't match with the way Quartile is calculated in Excel and histogram created using Ploltly so I dig further and stumbled upon following two links:
- C# Descriptive Statistic Class
- Statistics - check slide 12
Slide 12 in second link states "The position of the Pth percentile is given by (n + 1)P/100
, where n is the number of observations in the set."
So equivalent C# code from C# Descriptive Statistic Class is:
/// <summary>
/// Calculate percentile of a sorted data set
/// </summary>
/// <param name="sortedData"></param>
/// <param name="p"></param>
/// <returns></returns>
internal static double Percentile(double[] sortedData, double p)
{
// algo derived from Aczel pg 15 bottom
if (p >= 100.0d) return sortedData[sortedData.Length - 1];
double position = (sortedData.Length + 1) * p / 100.0;
double leftNumber = 0.0d, rightNumber = 0.0d;
double n = p / 100.0d * (sortedData.Length - 1) + 1.0d;
if (position >= 1)
{
leftNumber = sortedData[(int)Math.Floor(n) - 1];
rightNumber = sortedData[(int)Math.Floor(n)];
}
else
{
leftNumber = sortedData[0]; // first data
rightNumber = sortedData[1]; // first data
}
//if (leftNumber == rightNumber)
if (Equals(leftNumber, rightNumber))
return leftNumber;
double part = n - Math.Floor(n);
return leftNumber + part * (rightNumber - leftNumber);
} // end of internal function percentile
Test case (written in Visual Studio 2017):
static void Main()
{
double[] x = { 18, 18, 18, 18, 19, 20, 20, 20, 21, 22, 22, 23, 24, 26, 27, 32, 33, 49, 52, 56 };
var q1 = Percentile(x, 25);
var q2 = Percentile(x, 50);
var q3 = Percentile(x, 75);
var iqr = q3 - q1;
var (q1_mike, q2_mike, q3_mike) = Quartiles(x); //Uses named tuples instead of regular Tuple
var iqr_mike = q3_mike - q1_mike;
}
Result comparison:
You will notice that result in excel matches to the theory mentioned Statistics in slide 12.
- From code:
- From excel (matches q1, q2 and q3 values)