Percentiles and Spread
Median and Quartiles
Split sorted data into four equal parts using Q1, median (Q2), and Q3.
With n=9, the percentile positions (n−1)×p/100 land on exact integers (2,
4, 6), so no interpolation is needed — just index into the sorted list.
Loop over [25, 50, 75], compute each integer position, read sv[pos]. With
numpy, np.percentile(x, [25, 50, 75]) uses linear interpolation by
default; with exact-integer positions the result is identical.
By hand
n=9 gives positions (n−1)×0.25=2, (n−1)×0.5=4, (n−1)×0.75=6 — all
integers. Loop over percents=[25,50,75], compute pos = int((n-1)*p/100),
append sv[pos]. Unpack: q1=sv[2]=3, median=sv[4]=5, q3=sv[6]=7.
values = [4, 7, 2, 9, 1, 8, 5, 6, 3]
sv = sorted(values)
n = len(sv)
percents = [25, 50, 75]
quartiles = []
for p in percents:
pos = int((n - 1) * p / 100)
quartiles.append(sv[pos])
q1, median, q3 = quartiles
print('RESULT:', (q1, median, q3))
values ← [4, 7, 2, 9, 1, 8, 5, 6, 3]
1values = [4, 7, 2, 9, 1, 8, 5, 6, 3]2sv = sorted(values)values this step[4, 7, 2, 9, 1, 8, 5, 6, 3]valuessv ← [1, 2, 3, 4, 5, 6, 7, 8, 9]
1values = [4, 7, 2, 9, 1, 8, 5, 6, 3]2sv = sorted(values)3n = len(sv)values this step[1, 2, 3, 4, 5, 6, 7, 8, 9]svn ← 9
2sv = sorted(values)3n = len(sv)4percents = [25, 50, 75]values this step9npercents ← [25, 50, 75]
3n = len(sv)4percents = [25, 50, 75]5quartiles = []values this step[25, 50, 75]percentsquartiles ← []
4percents = [25, 50, 75]5quartiles = []6for p in percents:values this step[]quartilesp ← 25
5quartiles = []6for p in percents:7 pos = int((n - 1) * p / 100)values this step25ppos ← 2
6for p in percents:7 pos = int((n - 1) * p / 100)8 quartiles.append(sv[pos])values this step2posquartiles ← [3]
7 pos = int((n - 1) * p / 100)8 quartiles.append(sv[pos])9q1, median, q3 = quartilesvalues this step[] → [3]quartilesp ← 50
5quartiles = []6for p in percents:7 pos = int((n - 1) * p / 100)values this step25 → 50ppos ← 4
6for p in percents:7 pos = int((n - 1) * p / 100)8 quartiles.append(sv[pos])values this step2 → 4posquartiles ← [3, 5]
7 pos = int((n - 1) * p / 100)8 quartiles.append(sv[pos])9q1, median, q3 = quartilesvalues this step[3] → [3, 5]quartilesp ← 75
5quartiles = []6for p in percents:7 pos = int((n - 1) * p / 100)values this step50 → 75ppos ← 6
6for p in percents:7 pos = int((n - 1) * p / 100)8 quartiles.append(sv[pos])values this step4 → 6posquartiles ← [3, 5, 7]
7 pos = int((n - 1) * p / 100)8 quartiles.append(sv[pos])9q1, median, q3 = quartilesvalues this step[3, 5] → [3, 5, 7]quartilesfor p in percents:
5quartiles = []6for p in percents:7 pos = int((n - 1) * p / 100)median ← 5, q1 ← 3, q3 ← 7
8 quartiles.append(sv[pos])9q1, median, q3 = quartiles10print('RESULT:', (q1, median, q3))values this step5median3q17q3stdout ← RESULT: (3, 5, 7)
9q1, median, q3 = quartiles10print('RESULT:', (q1, median, q3))values this stepRESULT: (3, 5, 7)stdout
With the library
np.percentile(x, [25, 50, 75]) uses linear interpolation: position
= (n−1)×p/100; if fractional, it blends adjacent elements. With n=9 the
positions are exact integers so no blending occurs and the result equals the
naive index lookup. Values are float — cast with int() to match.
import numpy as np
from dalib.display import set_display
set_display()
x = [4, 7, 2, 9, 1, 8, 5, 6, 3]
q1, median, q3 = np.percentile(x, [25, 50, 75])
print('Q1:', int(q1))
print('median:', int(median))
print('Q3:', int(q3))
print('RESULT:', (int(q1), int(median), int(q3)))
Q1: 3
median: 5
Q3: 7
RESULT: (3, 5, 7)
Implementation notes
- The integer-position guarantee: choose n such that (n−1) is divisible by
4 (e.g. n=5→4, n=9→8, n=13→12). Then 0.25×(n−1), 0.5×(n−1), and
0.75×(n−1) are all integers and
int(...)is an exact match to numpy's linear interpolation. - When positions are not integers, numpy interpolates:
sv[floor(pos)] + frac × (sv[floor(pos)+1] − sv[floor(pos)]). Replicating this exactly in naive code requires the same formula. - Cross-reference:
median-search(ch01) for finding the median alone;interquartile-range(this chapter) for using Q1 and Q3 to measure spread.