Worked examples — Outlier detection and treatment
This page is the practice ground for the parent topic. We will not re-derive the formulas here — we will use them on every kind of dataset you could meet: clean data, contaminated data, skewed data, correlated data, and the trap cases where the "obvious" method quietly fails.
If any symbol below feels unfamiliar, it was built in the parent note. Prerequisite ideas about spreading and rescaling live in 2.1.02-Feature-scalingand-normalization; the anomaly-hunting cousin of this topic is 3.3.02-Anomaly-detection.
The scenario matrix
Every outlier problem falls into one of these case classes. The worked examples below each carry a tag like (C3) telling you which cell they fill.
| Cell | Case class | What makes it tricky | Covered by |
|---|---|---|---|
| C1 | Clean, roughly normal data | Baseline — methods should agree | Ex 1 |
| C2 | One extreme upper-tail value contaminating mean/σ | Z-score self-sabotages | Ex 2 |
| C3 | Skewed distribution, no true outlier | Method must not invent one | Ex 3 |
| C4 | Zero-spread / degenerate input | Division by | Ex 4 |
| C5 | Correlated 2-D data, "impossible combo" | Needs Mahalanobis, not Euclidean | Ex 5 |
| C6 | Varying-density clusters | Global fails, LOF succeeds | Ex 6 |
| C7 | Real-world word problem (treatment choice) | Detect and decide keep/remove/transform | Ex 7 |
| C8 | Lower-tail / negative outlier + exam boundary twist | Sign symmetry, left tail, on-the-fence | Ex 8 |
We define the symbols exactly as the parent did: = mean, = standard deviation, , = 25th/75th percentiles, , fences at and .
Univariate examples
Forecast: These look tame. Guess: nothing flagged, both methods agree.
- Compute and . . Population . Why this step? The z-score asks "how many spreads from center?", so we need center () and spread () first — and we use the population formula fixed above.
- Largest deviation is : . Why this step? If the most extreme point has , nothing else can exceed the threshold.
- IQR fences (linear interpolation). For these 8 sorted values
numpy.percentilegives , , . Upper fence ; lower fence . Why this step? IQR is the robust cross-check. Agreement builds trust.
Verify: All data lie in and the most extreme . Both methods say "no outliers." Forecast confirmed — this is what agreement looks like.
Forecast: Obvious outlier → surely z-score flags it? (Trap: guess yes.)
- and with the included. , population . Why this step? We must use the actual contaminated statistics — that is the whole point of the failure.
- z-score of 150: . Why this step? Compare against the usual rule.
- Verdict (z): NOT flagged. The outlier inflated , hiding itself. Why this step? We must state the consequence explicitly: the strict threshold isn't met, so the obviously-bad point survives — that is the failure we came to see.
- Now IQR. , , , upper fence . Why this step? Percentiles ignore the magnitude of extremes, so contamination can't fool them.
- Verdict (IQR): flagged. Why this step? This is the punchline: on the same data IQR succeeds where z-score failed, so we record which method to trust.
Figure 1 (below) — titled "Fig 1 (Ex 2)" — plots the eight ages. The dashed gray line is the cutoff (which the red point sits below, hence missed); the solid green line is the IQR upper fence at (which clearly clears, hence caught). The whole story of self-masking is in the gap between those two horizontal lines.

Verify: (missed) but (caught). Your gut beat the z-score; IQR agreed with your gut.
Forecast: Skewed data has a stretched right side. Guess: the z-score over-reacts to the , but IQR is calmer — and may be legitimate skew, not an error.
- z-score path. . Population variance , so . Then and . Why this step? We must actually compute the z-scores to compare them against IQR, not just wave at them.
- z verdict. z-score flags nothing at the strict rule; but note is already the extreme edge — on skewed data z-scores drift high because the long tail inflates and pulls right. Why this step? We turn the raw number into a decision: the strict rule says "keep," which is the point — a statistically extreme skew tail can still pass, so we must not lean on the z-score alone for skewed features.
- IQR (the skew-safe method). With linear interpolation
numpy.percentilegives , , , upper fence . Why this step? IQR anchors to ranks, so the long tail cannot distort the fence the way it distorts . - IQR verdict. flagged; kept. Why this step? We convert the fence into a per-point call so the disagreement with z-score is explicit: IQR flags exactly , z-score flags nothing.
- Domain sanity. A 12-second session is entirely plausible for a website. Why this step? Detection removal. A statistically extreme point on naturally skewed data is often a legitimate signal, not an error.
Verify: , , (z flags nothing); IQR fence flags only. The two methods disagree, and IQR is the trustworthy one here.
Forecast: All identical → no outliers. But how does the formula behave? Guess: division by zero somewhere.
- Compute . Every , so . Why this step? is the degenerate case the formula was never designed for.
- Attempt — undefined. Why this step? It exposes that the z-score is ill-defined when spread vanishes.
- Safe handling. Define: if , there are no outliers (all points identical). Code should guard
if std == 0: return no_outliers. Why this step? Every real pipeline must cover the zero-variance column, or it crashes at runtime.
Verify: , so the numerator too and the z-score is the undefined form ; the correct policy is "no outliers, drop constant column."
Multivariate example
Forecast: Euclidean sees "far in sqft." Mahalanobis also knows big houses should have more beds — so A breaks the pattern. Guess: Mahalanobis distance is large.
- Center the point. . Why this step? Distance is always measured from the center of the cloud.
- Invert the covariance. For above, . Why this step? "un-stretches and un-rotates" the ellipse so we can measure distance fairly across correlated axes (the whitening idea from the parent).
- Squared Mahalanobis distance . Plugging in : Numerator . So , . Why this step? The middle cross-term is positive precisely because A violates the positive correlation (much sqft, too few beds) — that violation is what inflates the distance.
- Threshold. With , the cutoff of is for . Here flagged. Why this step? follows a under normality, giving a principled cutoff instead of a hand-picked one.
Figure 2 (below) — titled "Fig 2 (Ex 5)" — draws the green ellipse of "typical" houses (the boundary, stretched and tilted by ) with the blue cloud inside it. The orange dot is the mean; the red dot is House A, sitting outside and below the ellipse because it has the sqft of a mansion but the bedrooms of a cottage. Euclidean distance would only see the horizontal gap; the ellipse's tilt is what condemns A.

Verify: → House A is a multivariate outlier even though 5000 sqft alone is unremarkable. Euclidean would have missed the pattern violation.
Density example
Forecast: A global z-score measures distance from the overall mean. Guess: it flags both and — but LOF, which looks locally, should clear the in-gap point and condemn the truly isolated .
- Global mean and z-scores. The 12 values are ; their sum is , so . Population variance , so . Then and . Why this step? We must compute the global statistics to see exactly what they flag, not assert it. (In a 2-D global-distance version using the mean point, both and look "far from center"; the 1-D z-scores make the trap concrete.)
- What z would do. At the rule z flags neither; loosen to and it flags only — but crucially a distance-from-mean view treats (in an empty gap) as suspicious even though it is a legitimate transition point. Why this step? We turn the two z-numbers into a verdict so the trap is explicit: the global rule either misses everything or (loosened) catches only , and a naive far-from-center view unfairly suspects — that mismatch motivates a local method.
- LOF setup (). For any point, on the line. We need, for each of , and their neighbors, the -nearest-neighbor distances. Why this step? LOF is built entirely from -nearest-neighbor distances, so we must fix and the distance metric before computing anything.
- Neighbors of . Nearest two are and : distances and . Its -distance is . But 's own two nearest () are at distances , so sits in a region where typical spacing is . Thus is ~ away from a neighborhood whose internal scale is ~. Why this step? LOF asks "is my local density far below my neighbors' density?" Here 's reachability distances (–) dwarf its neighbors' (), giving (an order of magnitude), so is a genuine outlier.
- Neighbors of . Nearest two are (distance ) and (distance ). Its reachability distances are . Its neighbors and live at cluster edges where their own -distances are . So 's density is lower than its neighbors', but only by a factor of a few — -ish in raw ratio, which after the reachability smoothing lands near a modest value, far below 's. Why this step? is sparser than the clusters but not catastrophically so — it is a transition region, exactly the case LOF is designed to spare.
- Verdict. Ranked by LOF, : is the true anomaly, is tolerable. A pure distance-from-mean method inverts or blurs this. Why this step? The whole example exists to produce a ranking decision; stating it makes the contrast with the global z-score (which suspected ) the memorable takeaway.
Figure 3 (below) — titled "Fig 3 (Ex 6)" — places all points on the line. Blue = the two dense clusters, green = the in-gap point (LOF small, kept), red = the far point (LOF large, flagged). The visual gap widths encode exactly the reachability distances used above.

Verify: , , , ; 's -distance () is ~ its neighbors' spacing () while 's () is only ~ its neighbors' (). Hence LOF ranks as the anomaly and spares , opposite to a naive "far from mean" flag on .
Word problem & lower-tail / exam twist
Forecast: The CEO is a real person, not an error. Guess: flagged statistically, but we should not delete a legitimate value — cap or transform instead.
- IQR detection (linear interpolation). For sorted values
numpy.percentilegives , , , upper fence . Why this step? Robust method, since one huge salary would wreck the mean. - Flag. statistically an outlier. Why this step? We convert the fence into a decision — the CEO's pay is unambiguously beyond it.
- Decision using the parent's framework. It is (a) legitimate (real CEO), (b) not a data error. So do not remove. For a linear model we cap (Winsorize) or log-transform to limit its leverage. Why this step? Detection is statistical; treatment is a business decision. Deleting a valid record biases the model.
- Winsorize at (linear-interpolation percentile). For sorted values, the th percentile sits at fractional rank , i.e. of the way from the 7th value () to the 8th (): . So ; order preserved.
Why this step? We must state the interpolation scheme (
numpy.percentiledefault) so the capped value is reproducible. Capping keeps the CEO "highest paid" (monotonicity) while shrinking the leverage. See downstream 4.1.03-Regularization which also tames extreme influence.
Verify: Fence flags ; treatment = cap not delete, capped value under linear interpolation. The value is kept but tamed.
Forecast: Left-tail and right-tail should behave symmetrically. Guess: IQR's lower fence catches ; the z-score again self-masks because inflates . And the symmetric set gives a clean integer fence.
- Part A — z-score. , population . Then . Why this step? Negative outliers give negative z-scores; we check , so sign does not change the rule but the magnitude still gets self-masked.
- Part A verdict (z). NOT flagged — the same self-masking failure as the high outlier in Ex 2, now on the left tail. Why this step? We make the sign-symmetry explicit: the mechanism (inflated ) is identical to Ex 2, only the tail differs.
- Part A — IQR (linear interpolation). Sorted: .
numpy.percentilegives , , , lower fence . Why this step? The lower fence is the mirror of the upper one; low outliers are caught by the lower fence, which people often forget to check. - Part A verdict (IQR). flagged. Symmetric to Ex 2 but on the negative side. Why this step? We record the punchline: always test both fences, or you silently miss every left-tail glitch.
- Part B — boundary. For ,
numpy.percentilegives , , , upper fence . Why this step? Exam questions love the " vs " edge — a point is an outlier only when strictly beyond the fence. - Part B answer. sits exactly on the fence (kept, not strictly greater); is flagged. Why this step? The boundary value itself is the trap; naming both the on-fence (, kept) and just-past (, flagged) cases removes the ambiguity.
Verify: Part A: , (missed) but (caught) — mirror of Ex 2. Part B: upper fence ; borderline-kept, flagged.
Recall
Recall Which method survives a contaminated mean?
Which detector self-masks a lone extreme value, and which one catches it? ::: The z-score self-masks (the outlier inflates , on either tail); IQR catches it because percentiles ignore extreme magnitudes.
Recall Why Mahalanobis over Euclidean?
What does Mahalanobis distance know that Euclidean does not? ::: It knows the correlations and scales via , so it flags points that violate the joint pattern (e.g., huge house, few beds) even when each coordinate alone looks normal.
Recall When does LOF beat a global z-score?
::: When density varies across the data — LOF compares a point to its local neighbors, so a point in a naturally sparse gap isn't flagged, while a truly isolated point far from any dense neighborhood is caught.
Recall Degenerate zero-variance column?
What is when , and what should the pipeline do? ::: is undefined; treat as "no outliers" and drop the constant column in 2.2.01-Feature-selection.
Recall Lower-tail outliers?
Which fence catches a negative glitch value? ::: The lower fence ; the z-score still self-masks on the left tail just as it does on the right.