Percentiles and Spread
Interquartile Range
The IQR is Q3 − Q1: the width of the middle 50% of sorted data. It is
robust to outliers because the extreme 25% on each end are excluded. Sort,
compute the two quartile positions (exact integers for n=9), read sv[pos],
subtract. With scipy, scipy.stats.iqr(x) uses linear interpolation
(matching np.percentile) by default.
By hand
Same n=9 data as median-and-quartiles. Positions pos_q1=2, pos_q3=6 are
exact integers — no interpolation. q1=sv[2]=3, q3=sv[6]=7, iqr=7−3=4.
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)
pos_q1 = int((n - 1) * 0.25)
pos_q3 = int((n - 1) * 0.75)
q1 = sv[pos_q1]
q3 = sv[pos_q3]
iqr = q3 - q1
print('RESULT:', iqr)
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)4pos_q1 = int((n - 1) * 0.25)values this step9npos_q1 ← 2
3n = len(sv)4pos_q1 = int((n - 1) * 0.25)5pos_q3 = int((n - 1) * 0.75)values this step2pos_q1pos_q3 ← 6
4pos_q1 = int((n - 1) * 0.25)5pos_q3 = int((n - 1) * 0.75)6q1 = sv[pos_q1]values this step6pos_q3q1 ← 3
5pos_q3 = int((n - 1) * 0.75)6q1 = sv[pos_q1]7q3 = sv[pos_q3]values this step3q1q3 ← 7
6q1 = sv[pos_q1]7q3 = sv[pos_q3]8iqr = q3 - q1values this step7q3iqr ← 4
7q3 = sv[pos_q3]8iqr = q3 - q19print('RESULT:', iqr)values this step4iqrstdout ← RESULT: 4
8iqr = q3 - q19print('RESULT:', iqr)values this stepRESULT: 4stdout
With the library
scipy.stats.iqr(x) defaults to rng=(25, 75) and linear interpolation,
matching np.percentile. With exact-integer positions the result is the
same as the naive subtraction. Showing Q1 and Q3 separately makes the
denominator and subtraction visible.
library.py
import numpy as np
from scipy.stats import iqr
from dalib.display import set_display
set_display()
x = [4, 7, 2, 9, 1, 8, 5, 6, 3]
q1, q3 = np.percentile(x, [25, 75])
result = iqr(x)
print('Q1:', int(q1))
print('Q3:', int(q3))
print('RESULT:', int(result))
Q1: 3
Q3: 7
RESULT: 4
Implementation notes
- IQR is robust: adding a large outlier shifts Q1/Q3 only if it pushes into the middle 50%, which is rare. Compare to std, which grows with every outlier.
scipy.stats.iqraccepts arngparameter if you want a different percentile spread (e.g.rng=(10, 90)for the interdecile range).- Cross-reference:
median-and-quartiles(this chapter) for the quartile positions;iqr-outlier-flags(python-data-cleaning ch06) for using IQR to flag outliers via the 1.5×IQR fence rule.