DEV Community

Alan Matthew
Alan Matthew

Posted on

Finding Outliers with the IQR Method: Formula, Quartile Gotchas, Code in Python/R/SQL, and a Free Calculator

Every dataset eventually produces a value that makes you stop and ask, "is that real?" A $9,000 order in a column of $40 orders. A heart rate of 300. A test score of 120 out of 100. Before you average, model, or plot anything, you need a defensible way to flag those values.

The IQR method (Tukey's fences) is the most widely used rule for this. It's simple, it needs no assumption about the shape of your data, and it's what most box plots are drawing under the hood.

This post covers:

  • What the IQR and the fences are
  • A fully worked example you can verify by hand
  • A gotcha that trips up almost everyone: different tools compute quartiles differently, and that can change which points get flagged
  • Code in Python, pandas, R, Excel and SQL
  • How the IQR rule compares with z-scores and MAD
  • Where the IQR rule fails, and what to do with an outlier once you've found one
  • A free calculator if you want to paste data and see the result

The building blocks: quartiles and the IQR

Sort your data. The quartiles split it into four chunks:

  • Q1 (25th percentile): a quarter of the values fall below it
  • Q2 (the median): half fall below it
  • Q3 (75th percentile): three quarters fall below it

The interquartile range is the width of the middle half of your data:

IQR = Q3 − Q1
Enter fullscreen mode Exit fullscreen mode

Why does this matter for outlier detection? Because Q1 and Q3 barely move when you add one wild value at the end. The mean and standard deviation, by contrast, get dragged toward the outlier. A yardstick that isn't distorted by the thing you're measuring is exactly what you want.

Tukey's fences

John Tukey proposed drawing "fences" a fixed multiple of the IQR beyond the quartiles:

Lower inner fence = Q1 − 1.5 × IQR
Upper inner fence = Q3 + 1.5 × IQR

Lower outer fence = Q1 − 3 × IQR
Upper outer fence = Q3 + 3 × IQR
Enter fullscreen mode Exit fullscreen mode

Then classify each point:

Where the value falls Label
Inside the inner fences Normal
Beyond an inner fence, but inside the outer fence Mild outlier
Beyond an outer fence Extreme outlier

On a box plot, the whiskers stop at the most extreme points that are still inside the inner fences, and anything beyond them is drawn as an individual dot.

Where does 1.5 come from?

It's a convention, not a theorem. Tukey picked it as a practical balance. For normally distributed data, the 1.5 × IQR fences sit at about ±2.7 standard deviations from the mean, so roughly 0.7% of perfectly ordinary observations get flagged. I checked this by simulating 100,000 normal values and got 0.74%. The 3 × IQR fences sit near ±4.7 standard deviations, flagging only about 0.0002% (two in a million) of normal data.

That last point matters: on normal data, the 1.5 rule will flag roughly 1 in 140 observations even though nothing is wrong. With 10,000 rows you should expect around 70 "outliers" from pure chance. Treat the flags as candidates for inspection, not verdicts.

Worked example

A teacher records 12 test scores:

55, 62, 68, 70, 72, 75, 78, 80, 85, 88, 92, 120
Enter fullscreen mode Exit fullscreen mode

1. Sort. Already sorted.

2. Median. With n = 12, average the 6th and 7th values:

Q2 = (75 + 78) / 2 = 76.5
Enter fullscreen mode Exit fullscreen mode

3. Q1. Median of the lower half (55, 62, 68, 70, 72, 75):

Q1 = (68 + 70) / 2 = 69
Enter fullscreen mode Exit fullscreen mode

4. Q3. Median of the upper half (78, 80, 85, 88, 92, 120):

Q3 = (85 + 88) / 2 = 86.5
Enter fullscreen mode Exit fullscreen mode

5. IQR.

IQR = 86.5 − 69 = 17.5
Enter fullscreen mode Exit fullscreen mode

6. Inner fences.

Lower = 69   − 1.5 × 17.5 = 42.75
Upper = 86.5 + 1.5 × 17.5 = 112.75
Enter fullscreen mode Exit fullscreen mode

7. Outer fences.

Lower = 69   − 3 × 17.5 = 16.5
Upper = 86.5 + 3 × 17.5 = 139
Enter fullscreen mode Exit fullscreen mode

8. Classify. Everything from 55 to 92 is between 42.75 and 112.75, so it's normal. The score 120 is above 112.75 but below 139, so it's a mild outlier.

Is 120 a typo, extra credit, or a legitimately great student? The method can't tell you. It only tells you where to look.

The gotcha: there is no single "the" quartile

Here is the part that causes the most confusion. There are many accepted definitions of a quartile, and software disagrees.

For the 12 scores above, here is what different conventions give. I computed these with NumPy's percentile and its method argument:

Method Q1 Q3 IQR Lower fence Upper fence
Median of halves (textbook) 69 86.5 17.5 42.75 112.75
Linear interpolation (NumPy default, Excel QUARTILE.INC) 69.5 85.75 16.25 45.125 110.125
weibull (Excel QUARTILE.EXC, R type = 6) 68.5 87.25 18.75 40.375 115.375

For this dataset, 120 is flagged under all three. But watch what happens when the borderline value is a little smaller. Change 120 to 111:

Method Upper fence Is 111 flagged?
Median of halves 112.75 No
Linear (NumPy default) 110.125 Yes
weibull (Excel .EXC) 115.375 No

Same data, same "1.5 × IQR rule", different conclusions. This is why you should:

  1. Know which quartile convention your course, tool or team uses, and say so when you report results.
  2. Be suspicious of points sitting right at a fence. A value that is flagged under one convention and not another is a borderline case, not a clear outlier.

Odd sample sizes

When n is odd there is a real median value, and textbooks split on what to do with it:

  • Inclusive: include the median in both halves
  • Exclusive: leave it out of both halves

Take 11 values: 10, 12, 14, 15, 18, 21, 22, 23, 24, 25, 95. The median is 21.

Convention Q1 Q3 IQR Upper inner fence Upper outer fence
Median included in both halves 14.5 23.5 9 37 50.5
Median excluded 14 24 10 39 54

Here 95 is an extreme outlier under both, but the fences move by a few units. If you're checking homework against an answer key, match the convention the key uses.

Code

Python (NumPy)

import numpy as np

def iqr_outliers(data, k=1.5, method="linear"):
    x = np.asarray(data, dtype=float)
    q1, q3 = np.percentile(x, [25, 75], method=method)
    iqr = q3 - q1
    lower, upper = q1 - k * iqr, q3 + k * iqr
    mask = (x < lower) | (x > upper)
    return {
        "q1": q1, "q3": q3, "iqr": iqr,
        "lower": lower, "upper": upper,
        "outliers": x[mask].tolist(),
    }

scores = [55, 62, 68, 70, 72, 75, 78, 80, 85, 88, 92, 120]

# "hazen" matches the median-of-halves result for this dataset
print(iqr_outliers(scores, method="hazen"))
# {'q1': 69.0, 'q3': 86.5, 'iqr': 17.5, 'lower': 42.75, 'upper': 112.75, 'outliers': [120.0]}
Enter fullscreen mode Exit fullscreen mode

A note on method=: it needs NumPy 1.22 or later. On older versions the argument is called interpolation and has fewer options. Also, hazen happens to reproduce the median-of-halves quartiles for this even-sized dataset, but it is not the same rule in general. For odd n and other layouts it can differ, so check it against a hand calculation for your own data.

To get mild and extreme labels:

def classify(x, q1, q3):
    iqr = q3 - q1
    if x < q1 - 3 * iqr or x > q3 + 3 * iqr:
        return "extreme"
    if x < q1 - 1.5 * iqr or x > q3 + 1.5 * iqr:
        return "mild"
    return "normal"

r = iqr_outliers(scores, method="hazen")
for s in scores:
    print(s, classify(s, r["q1"], r["q3"]))
Enter fullscreen mode Exit fullscreen mode

pandas: do it per group

This is the mistake I see most often in real data work. If your dataset has groups with very different scales, a single global IQR will either miss obvious problems or flag whole groups. Compute fences within each group:

import pandas as pd

df = pd.DataFrame({
    "group": ["a"] * 6 + ["b"] * 6,
    "value": [10, 11, 12, 13, 14, 40, 100, 102, 105, 98, 101, 160],
})

def flag(s, k=1.5):
    q1, q3 = s.quantile([0.25, 0.75])
    iqr = q3 - q1
    return (s < q1 - k * iqr) | (s > q3 + k * iqr)

df["outlier_global"] = flag(df["value"])
df["outlier_by_group"] = df.groupby("group")["value"].transform(flag)
print(df)
Enter fullscreen mode Exit fullscreen mode

On this toy data, the global rule flags nothing, because group b is so much larger than group a that the pooled IQR is huge. The grouped rule correctly flags 40 (in group a) and 160 (in group b).

R

x <- c(55, 62, 68, 70, 72, 75, 78, 80, 85, 88, 92, 120)

boxplot.stats(x)$out   # uses Tukey hinges, the same as the box plot

# manual version (quantile() defaults to type = 7, i.e. linear interpolation)
q <- quantile(x, c(0.25, 0.75), type = 7)
iqr <- q[2] - q[1]
x[x < q[1] - 1.5 * iqr | x > q[2] + 1.5 * iqr]
Enter fullscreen mode Exit fullscreen mode

Excel / Google Sheets

Q1:    =QUARTILE.INC(A1:A12, 1)
Q3:    =QUARTILE.INC(A1:A12, 3)
IQR:   =Q3 - Q1
Lower: =Q1 - 1.5*IQR
Upper: =Q3 + 1.5*IQR
Enter fullscreen mode Exit fullscreen mode

Swap in QUARTILE.EXC if you need the exclusive convention. Use conditional formatting with a formula like =OR(A1<$lower, A1>$upper) to highlight flagged cells.

SQL (PostgreSQL)

WITH q AS (
  SELECT
    percentile_cont(0.25) WITHIN GROUP (ORDER BY score) AS q1,
    percentile_cont(0.75) WITHIN GROUP (ORDER BY score) AS q3
  FROM exam_scores
)
SELECT s.*
FROM exam_scores s, q
WHERE s.score < q.q1 - 1.5 * (q.q3 - q.q1)
   OR s.score > q.q3 + 1.5 * (q.q3 - q.q1);
Enter fullscreen mode Exit fullscreen mode

percentile_cont uses linear interpolation, so it matches NumPy's default and Excel's QUARTILE.INC.

IQR vs z-score vs MAD

You'll often see a second outlier rule: flag anything more than 2 or 3 standard deviations from the mean. It has a structural weakness: the outliers contaminate the mean and the standard deviation you use to detect them. This is called masking.

Try it on this small sample:

10, 11, 12, 12, 13, 13, 14, 15, 60, 65
Enter fullscreen mode Exit fullscreen mode

Two values are obviously far from the rest. But:

  • Mean = 22.5, standard deviation = 21.16
  • The z-score of 65 is only 2.01, and 60 gets 1.77. A "|z| > 3" rule flags nothing.
  • In fact, for a sample of 10 the largest possible |z| is (n − 1) / √n ≈ 2.85, so a cutoff of 3 can never fire.
  • The IQR rule (linear quartiles) gives Q1 = 12, Q3 = 14.75, IQR = 2.75, and fences of 7.875 and 18.875. It flags both 60 and 65.

A third option is the modified z-score based on the median absolute deviation (MAD):

M = 0.6745 × (x − median) / MAD
Enter fullscreen mode Exit fullscreen mode

The common cutoff is |M| > 3.5. On the same sample the median is 13 and the MAD is 1.5, which gives M ≈ 21 for 60 and M ≈ 23 for 65. Both are flagged emphatically.

import numpy as np

x = np.array([10, 11, 12, 12, 13, 13, 14, 15, 60, 65])
med = np.median(x)
mad = np.median(np.abs(x - med))
m = 0.6745 * (x - med) / mad
print(x[np.abs(m) > 3.5])   # [60 65]
Enter fullscreen mode Exit fullscreen mode

Quick comparison:

Method Assumes normality? Resistant to the outliers themselves? Notes
z-score Yes, roughly No (masking) Needs large n for a cutoff of 3 to be usable
IQR / Tukey No Yes Symmetric fences, so can struggle with heavy skew
MAD / modified z No Yes Good default for small, messy samples

Where the IQR rule breaks down

1. Skewed data. The fences are symmetric around the middle half of the data. For a right-skewed variable (income, response times, order values), the long tail looks like a pile of outliers. I generated 1,000 values from a lognormal distribution (a standard model for skewed positive data) and the 1.5 × IQR rule flagged 92 of them (9.2%), none of which were errors. After taking the log of the data, which makes it normal, the same rule flagged 9 (0.9%). If your variable is naturally skewed, transform first or use a method designed for skew, such as the adjusted boxplot.

2. Tiny samples. With n of 4 or 5, quartiles are hardly defined, and one value can swing Q1 or Q3 a lot. The results are fragile.

3. Multimodal data. If you have two real clusters, the IQR spans the gap between them and the fences become meaningless. Split by group first.

4. Discrete or heavily tied data. If most values are identical, the IQR can be zero, which makes the fences collapse onto the quartiles and flags every other value.

5. Time series. A value can be perfectly normal for the series overall but anomalous for its season or trend. Detrend or de-seasonalize first.

You found an outlier. Now what?

Detection is the easy part. Before touching the value:

  1. Check for an error. Typos, unit mix-ups (kg vs lb), sensor glitches and import bugs cause a lot of outliers. If you can trace it to an error, fix it or remove it, and write down why.
  2. Check whether it's real. A billionaire in an income survey is a genuine observation. Deleting it changes what your analysis says about the population.
  3. Decide based on your goal. Options, roughly from least to most invasive:
    • Keep it and use robust statistics (median, IQR, trimmed mean)
    • Keep it and run the analysis both with and without, reporting both
    • Transform the variable (log, square root)
    • Winsorize (cap values at a percentile)
    • Remove it, with documented justification
  4. Document the rule. Report the multiplier, the quartile convention and the number of points affected, so someone else can reproduce your result.

Removing a point just because it failed a rule, with no investigation, is how analyses quietly become wrong.

Try it without writing code

If you just want to paste a list of numbers and see the fences, this free tool does it:

IQR Outlier Calculator

IQR Outlier Calculator: Find Outliers with Q1, Q3 & Fences

Free IQR outlier calculator instantly detects mild and extreme outliers. Returns Q1, Q3, IQR, inner and outer fences, and step-by-step LaTeX formulas for any dataset.

favicon takemystatisticsclassforme.us

What it offers:

  • Paste your dataset separated by commas, spaces or newlines (minimum 4 values)
  • Choose a 1.5 × IQR fence (standard) or 3 × IQR (extreme outliers only)
  • Choose an inclusive or exclusive quartile method
  • See the five-number summary, IQR, inner and outer fences, and a box plot
  • Every data point is classified as normal, mild outlier or extreme outlier
  • A step-by-step breakdown of the math

Paste the 12 test scores from the worked example above. You should get Q1 = 69, Q3 = 86.5, IQR = 17.5, fences of 42.75 and 112.75, and 120 flagged as a mild outlier. Because of the quartile-convention issue I described above, make sure the calculator's quartile setting matches the convention you're comparing against.

Common mistakes

  • Not sorting first when computing quartiles by hand.
  • Mixing quartile conventions between your textbook, calculator, Excel and Python, then wondering why the numbers differ in the second decimal.
  • Using 1.5 × IQR when you wanted extreme outliers. The standard rule flags mild ones too. Use 3 × IQR for extreme only.
  • Running it on categorical data. The rule needs meaningful numeric values.
  • Computing one global IQR across heterogeneous groups. Do it per group.
  • Deleting flagged points automatically instead of investigating them.
  • Applying it to strongly skewed data without a transform.
  • Reporting "outliers were removed" with no rule, no count and no justification.

How to report it

A clear, reproducible statement looks like this:

Outliers were identified using Tukey's rule (values below Q1 − 1.5 × IQR or above Q3 + 1.5 × IQR, with quartiles computed by linear interpolation). One of 12 observations (score = 120) was flagged as a mild outlier. It was retained after verification against the original records, and the analysis was repeated without it as a sensitivity check.

FAQ

Is 1.5 × IQR always the right multiplier?
No. It is a convention that works reasonably well for roughly symmetric data. For small, noisy samples people sometimes use 3. For very large datasets, 1.5 will flag many perfectly good points, so you may want a larger multiplier or a different method.

Can I use it on non-normal data?
Yes, that's one of its main advantages. It relies on order statistics, not on a normal distribution. It just works best on roughly symmetric data. For heavily skewed data, transform first.

How many data points do I need?
Technically 4, which is what many tools require. Practically, treat anything below about 10 to 15 values as fragile.

What's the difference between a mild and an extreme outlier?
A mild outlier lies beyond the inner fence (1.5 × IQR) but inside the outer fence (3 × IQR). An extreme outlier lies beyond the outer fence.

Should I always remove outliers?
No. First find out whether they are errors or real observations.

TL;DR

  • IQR = Q3 − Q1, and the fences are Q1 − 1.5·IQR and Q3 + 1.5·IQR (use 3 for extreme).
  • The rule needs no normality assumption and isn't distorted by the outliers it's hunting, unlike a z-score cutoff.
  • Quartile definitions differ between tools. Borderline points can flip, so state your convention.
  • Compute fences per group, and transform skewed data first.
  • A flag is a reason to investigate, not a reason to delete.

If you try the calculator or run the code above on your own data, I'd like to hear what you find. Questions about skewed data, grouped data or choosing a multiplier are welcome in the comments.

Top comments (0)