Flag values that lie outside Tukey's 1.5×IQR fences. Compute Q1 and Q3 (the 25th and 75th percentiles), set fences at Q1 − 1.5×IQR and Q3 + 1.5×IQR, then mark any value strictly outside those fences as an outlier. By hand, sort the list and pick the quartile positions directly. With pandas, use Series.quantile() to get the same values in one call.

By hand

Sort the values, then compute Q1 and Q3 by picking the positions at (n−1)×0.25 and (n−1)×0.75 in the sorted array. With n=9 values, these positions are (8)×0.25 = 2 and (8)×0.75 = 6 — exact integers, so no interpolation is needed. The naive half picks sv[2] and sv[6] directly using int((n-1)*0.25), which matches pandas' default linear-interpolation quantile exactly for this data. Fences: lo = Q1 − 1.5×IQR, hi = Q3 + 1.5×IQR. Flag any value where v < lo or v > hi.

naive.py
Replay: real traced execution (multi-file project)
values = [4, -10, 8, 50, 6, 12, 10, 16, 14]
sv = sorted(values)
n = len(sv)
q1 = sv[int((n - 1) * 0.25)]
q3 = sv[int((n - 1) * 0.75)]
iqr = q3 - q1
lo = q1 - 1.5 * iqr
hi = q3 + 1.5 * iqr
result = []
for v in values:
    result.append(v < lo or v > hi)
print('RESULT:', result)
  1. values ← [4, -10, 8, 50, 6, 12, 10, 16, 14]

    1values = [4, -10, 8, 50, 6, 12, 10, 16, 14]2sv = sorted(values)
    values this step[4, -10, 8, 50, 6, 12, 10, 16, 14]values
  2. sv ← [-10, 4, 6, 8, 10, 12, 14, 16, 50]

    1values = [4, -10, 8, 50, 6, 12, 10, 16, 14]2sv = sorted(values)3n = len(sv)
    values this step[-10, 4, 6, 8, 10, 12, 14, 16, 50]sv
  3. n ← 9

    2sv = sorted(values)3n = len(sv)4q1 = sv[int((n - 1) * 0.25)]
    values this step9n
  4. q1 ← 6

    3n = len(sv)4q1 = sv[int((n - 1) * 0.25)]5q3 = sv[int((n - 1) * 0.75)]
    values this step6q1
  5. q3 ← 14

    4q1 = sv[int((n - 1) * 0.25)]5q3 = sv[int((n - 1) * 0.75)]6iqr = q3 - q1
    values this step14q3
  6. iqr ← 8

    5q3 = sv[int((n - 1) * 0.75)]6iqr = q3 - q17lo = q1 - 1.5 * iqr
    values this step8iqr
  7. lo ← -6.0

    6iqr = q3 - q17lo = q1 - 1.5 * iqr8hi = q3 + 1.5 * iqr
    values this step-6.0lo
  8. hi ← 26.0

    7lo = q1 - 1.5 * iqr8hi = q3 + 1.5 * iqr9result = []
    values this step26.0hi
  9. result ← []

    8hi = q3 + 1.5 * iqr9result = []10for v in values:
    values this step[]result
  10. v ← 4

    9result = []10for v in values:11    result.append(v < lo or v > hi)
    values this step4v
  11. result ← [False]

    10for v in values:11    result.append(v < lo or v > hi)12print('RESULT:', result)
    values this step[] [False]result
  12. v ← -10

    9result = []10for v in values:11    result.append(v < lo or v > hi)
    values this step4 -10v
  13. result ← [False, True]

    10for v in values:11    result.append(v < lo or v > hi)12print('RESULT:', result)
    values this step[False] [False, True]result
  14. v ← 8

    9result = []10for v in values:11    result.append(v < lo or v > hi)
    values this step-10 8v
  15. result ← [False, True, False]

    10for v in values:11    result.append(v < lo or v > hi)12print('RESULT:', result)
    values this step[False, True] [False, True, False]result
  16. v ← 50

    9result = []10for v in values:11    result.append(v < lo or v > hi)
    values this step8 50v
  17. result ← [False, True, False, True]

    10for v in values:11    result.append(v < lo or v > hi)12print('RESULT:', result)
    values this step[False, True, False] [False, True, False, True]result
  18. v ← 6

    9result = []10for v in values:11    result.append(v < lo or v > hi)
    values this step50 6v
  19. result ← [False, True, False, True, False]

    10for v in values:11    result.append(v < lo or v > hi)12print('RESULT:', result)
    values this step[False, True, False, True] [False, True, False, True, False]result
  20. v ← 12

    9result = []10for v in values:11    result.append(v < lo or v > hi)
    values this step6 12v
  21. result ← [False, True, False, True, False, False]

    10for v in values:11    result.append(v < lo or v > hi)12print('RESULT:', result)
    values this step[False, True, False, True, False] [False, True, False, True, False, False]result
  22. v ← 10

    9result = []10for v in values:11    result.append(v < lo or v > hi)
    values this step12 10v
  23. result ← [False, True, False, True, False, False, False]

    10for v in values:11    result.append(v < lo or v > hi)12print('RESULT:', result)
    values this step[False, True, False, True, False, False] [False, True, False, True, False, False, False]result
  24. v ← 16

    9result = []10for v in values:11    result.append(v < lo or v > hi)
    values this step10 16v
  25. result ← [False, True, False, True, False, False, False, False]

    10for v in values:11    result.append(v < lo or v > hi)12print('RESULT:', result)
    values this step[False, True, False, True, False, False, False] [False, True, False, True, False, False, False, False]result
  26. v ← 14

    9result = []10for v in values:11    result.append(v < lo or v > hi)
    values this step16 14v
  27. result ← [False, True, False, True, False, False, False, False, False]

    10for v in values:11    result.append(v < lo or v > hi)12print('RESULT:', result)
    values this step[False, True, False, True, False, False, False, False] [False, True, False, True, False, False, False, False, False]result
  28. for v in values:

    9result = []10for v in values:11    result.append(v < lo or v > hi)
  29. stdout ← RESULT: [False, True, False, True, False, False, False, False, False]

    11    result.append(v < lo or v > hi)12print('RESULT:', result)
    values this stepRESULT: [False, True, False, True, False, False, False, False, False]stdout

With pandas

Series.quantile(p) uses linear interpolation by default. For this data the 25th and 75th percentile positions land on exact indices, so the result equals the naive pick. Build the fence flags with (df['x'] < lo) | (df['x'] > hi).

library.py
import pandas as pd
from dalib.display import set_display
set_display()

values = [4, -10, 8, 50, 6, 12, 10, 16, 14]
df = pd.DataFrame({'x': values})
q1 = df['x'].quantile(0.25)
q3 = df['x'].quantile(0.75)
iqr = q3 - q1
lo = q1 - 1.5 * iqr
hi = q3 + 1.5 * iqr
flags = (df['x'] < lo) | (df['x'] > hi)
result = flags.tolist()
print('q1:', q1, 'q3:', q3, 'iqr:', iqr)
print('lo:', lo, 'hi:', hi)
print('values:', df['x'].tolist())
print('RESULT:', result)
q1: 6.0 q3: 14.0 iqr: 8.0
lo: -6.0 hi: 26.0
values: [4, -10, 8, 50, 6, 12, 10, 16, 14]
RESULT: [False, True, False, True, False, False, False, False, False]

Implementation notes

  • The quartile method matters for parity: int((n-1)*p) gives the exact position only when (n-1)*p is an integer. Choosing n=9 makes both Q1 and Q3 positions integers, so naive and pandas agree without approximation.
  • Values exactly on a fence are not flagged — the condition is strict (< and >), consistent with Tukey's original definition.
  • The 1.5×IQR multiplier is Tukey's standard threshold; use 3×IQR for "extreme" outlier fences.
  • Cross-reference: stats-percentiles (statistics chapter) for a deeper look at percentile methods and interpolation.