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.

naive.py
Replay: real traced execution (multi-file project)
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))
  1. 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]values
  2. sv ← [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]sv
  3. n ← 9

    2sv = sorted(values)3n = len(sv)4percents = [25, 50, 75]
    values this step9n
  4. percents ← [25, 50, 75]

    3n = len(sv)4percents = [25, 50, 75]5quartiles = []
    values this step[25, 50, 75]percents
  5. quartiles ← []

    4percents = [25, 50, 75]5quartiles = []6for p in percents:
    values this step[]quartiles
  6. p ← 25

    5quartiles = []6for p in percents:7    pos = int((n - 1) * p / 100)
    values this step25p
  7. pos ← 2

    6for p in percents:7    pos = int((n - 1) * p / 100)8    quartiles.append(sv[pos])
    values this step2pos
  8. quartiles ← [3]

    7    pos = int((n - 1) * p / 100)8    quartiles.append(sv[pos])9q1, median, q3 = quartiles
    values this step[] [3]quartiles
  9. p ← 50

    5quartiles = []6for p in percents:7    pos = int((n - 1) * p / 100)
    values this step25 50p
  10. pos ← 4

    6for p in percents:7    pos = int((n - 1) * p / 100)8    quartiles.append(sv[pos])
    values this step2 4pos
  11. quartiles ← [3, 5]

    7    pos = int((n - 1) * p / 100)8    quartiles.append(sv[pos])9q1, median, q3 = quartiles
    values this step[3] [3, 5]quartiles
  12. p ← 75

    5quartiles = []6for p in percents:7    pos = int((n - 1) * p / 100)
    values this step50 75p
  13. pos ← 6

    6for p in percents:7    pos = int((n - 1) * p / 100)8    quartiles.append(sv[pos])
    values this step4 6pos
  14. quartiles ← [3, 5, 7]

    7    pos = int((n - 1) * p / 100)8    quartiles.append(sv[pos])9q1, median, q3 = quartiles
    values this step[3, 5] [3, 5, 7]quartiles
  15. for p in percents:

    5quartiles = []6for p in percents:7    pos = int((n - 1) * p / 100)
  16. median ← 5, q1 ← 3, q3 ← 7

    8    quartiles.append(sv[pos])9q1, median, q3 = quartiles10print('RESULT:', (q1, median, q3))
    values this step5median3q17q3
  17. stdout ← 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.

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.