Outliers and Ranges
IQR Outlier Flags
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.
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)
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]valuessv ← [-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]svn ← 9
2sv = sorted(values)3n = len(sv)4q1 = sv[int((n - 1) * 0.25)]values this step9nq1 ← 6
3n = len(sv)4q1 = sv[int((n - 1) * 0.25)]5q3 = sv[int((n - 1) * 0.75)]values this step6q1q3 ← 14
4q1 = sv[int((n - 1) * 0.25)]5q3 = sv[int((n - 1) * 0.75)]6iqr = q3 - q1values this step14q3iqr ← 8
5q3 = sv[int((n - 1) * 0.75)]6iqr = q3 - q17lo = q1 - 1.5 * iqrvalues this step8iqrlo ← -6.0
6iqr = q3 - q17lo = q1 - 1.5 * iqr8hi = q3 + 1.5 * iqrvalues this step-6.0lohi ← 26.0
7lo = q1 - 1.5 * iqr8hi = q3 + 1.5 * iqr9result = []values this step26.0hiresult ← []
8hi = q3 + 1.5 * iqr9result = []10for v in values:values this step[]resultv ← 4
9result = []10for v in values:11 result.append(v < lo or v > hi)values this step4vresult ← [False]
10for v in values:11 result.append(v < lo or v > hi)12print('RESULT:', result)values this step[] → [False]resultv ← -10
9result = []10for v in values:11 result.append(v < lo or v > hi)values this step4 → -10vresult ← [False, True]
10for v in values:11 result.append(v < lo or v > hi)12print('RESULT:', result)values this step[False] → [False, True]resultv ← 8
9result = []10for v in values:11 result.append(v < lo or v > hi)values this step-10 → 8vresult ← [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]resultv ← 50
9result = []10for v in values:11 result.append(v < lo or v > hi)values this step8 → 50vresult ← [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]resultv ← 6
9result = []10for v in values:11 result.append(v < lo or v > hi)values this step50 → 6vresult ← [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]resultv ← 12
9result = []10for v in values:11 result.append(v < lo or v > hi)values this step6 → 12vresult ← [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]resultv ← 10
9result = []10for v in values:11 result.append(v < lo or v > hi)values this step12 → 10vresult ← [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]resultv ← 16
9result = []10for v in values:11 result.append(v < lo or v > hi)values this step10 → 16vresult ← [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]resultv ← 14
9result = []10for v in values:11 result.append(v < lo or v > hi)values this step16 → 14vresult ← [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]resultfor v in values:
9result = []10for v in values:11 result.append(v < lo or v > hi)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).
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)*pis 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.