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

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.

naive.py
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))
library.py
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.