Danh sách bài viết

Bài 12: Xử lý outlier — IQR và Z-score

Phát hiện và xử lý outlier với Z-score và IQR: ngưỡng |z| > 3, công thức Q1 - 1.5*IQR / Q3 + 1.5*IQR, box plot, chiến lược drop / cap / transform / robust model, và khi nào KHÔNG nên loại outlier.

24/05/2026
13 phút đọc
0 lượt xem
1

Mục tiêu bài học

Sau bài này, bạn sẽ:

  • Hiểu outlier là gì và phân biệt "outlier do lỗi" với "outlier là sự kiện hiếm thực sự".
  • Phát hiện outlier bằng Z-score và IQR, biết ưu/nhược của mỗi method.
  • Đọc được box plot và liên hệ whisker với công thức IQR.
  • Chọn chiến lược xử lý: drop, cap (winsorize), transform, hoặc đổi model robust.
  • Biết khi nào outlier tín hiệu cần giữ (fraud, anomaly) chứ không phải nhiễu.
2

Outlier là gì — và không đồng nghĩa "sai"

Outlier là giá trị lệch xa so với phần lớn dữ liệu của cùng một feature. "Lệch xa" định nghĩa qua một ngưỡng thống kê (Z-score, IQR) hoặc qua visualize (box plot, scatter).

Điểm cần nhớ ngay: outlier KHÔNG đồng nghĩa với "sai". Có hai nguồn gốc rất khác nhau:

  • Lỗi đo đạc / lỗi nhập liệu — sensor hỏng đọc nhiệt độ 9999°C; form nhập nhầm tuổi 250; thiếu dấu phẩy thập phân biến 1.5 thành 15. Loại này nên loại hoặc sửa.
  • Sự kiện hiếm có thật — trong dataset lương, một tỉ phú với thu nhập gấp 10.000 lần median vẫn là dữ liệu đúng. Nếu task là dự đoán siêu giàu hoặc phát hiện gian lận, đây chính là tín hiệu, không phải nhiễu.

Cùng một con số có thể là rác trong bài toán này nhưng là target trong bài toán khác. Vì vậy bước đầu tiên luôn là phát hiện — quyết định xử lý nằm ở bước sau và phụ thuộc domain.

3

Vì sao outlier ảnh hưởng đến model

Outlier kéo lệch các thống kê dựa trên tổng/bình phương, qua đó ảnh hưởng đến mọi bước downstream:

  • Mean và std bị kéo. Mean nhạy với outlier (một giá trị cực lớn đẩy \( \mu \) lên); std càng nhạy hơn vì có bình phương trong công thức. Hậu quả: StandardScaler ra \( z \) sai — phần lớn data dồn vào dải hẹp, outlier vẫn nổi bật.
  • Linear regression bị bias mạnh. Loss MSE \( \frac{1}{n}\sum (y_i - \hat{y}_i)^2 \) phạt sai số theo bình phương — outlier sai 100 đơn vị bị phạt gấp 10.000 lần sai 1 đơn vị. Model "ưu tiên" giảm sai số ở outlier và kéo đường hồi quy lệch khỏi phần đa số.
  • KNN, K-Means: distance bị skew. Khoảng cách Euclidean cộng dồn chênh lệch trên mọi chiều; một feature có outlier cực đoan thống trị tổng, làm các feature khác mất tác dụng.
  • Tree-based ít nhạy hơn. Decision Tree, Random Forest, Gradient Boosting split theo ngưỡng rời rạc — một outlier chỉ đẩy ngưỡng split ra biên, không ảnh hưởng theo bình phương. Vẫn có thể overfit vào leaf chứa outlier nếu không giới hạn min_samples_leaf.
4

Hai phương pháp phát hiện cơ bản

Ở mức cơ bản, hai phương pháp định lượng dùng cho từng feature số (univariate):

  • Z-score — đo "cách mean bao nhiêu lần std", giả định data gần phân phối chuẩn.
  • IQR (Interquartile Range) — đo dựa trên quartile, không giả định phân phối, robust với skew.

Hai mục tiếp theo đi vào công thức và code của từng cái.

5

Z-score method

Z-score của một giá trị \( x \) là số lần std mà nó cách mean:

\[ z = \frac{x - \mu}{\sigma} \]

Trong đó \( \mu \) là mean, \( \sigma \) là std của feature đó (tính trên train set).

Ngưỡng phổ biến: đánh dấu \( x \) là outlier nếu \( |z| > 3 \). Với phân phối chuẩn, vùng \( |z| > 3 \) chỉ chiếm khoảng 0.27% — phần đuôi rất hiếm. Có thể chọn 2.5 (gắt hơn, ~1.24%) hoặc 4 (lỏng hơn, ~0.0063%) tuỳ ngữ cảnh.

Code Python:

import numpy as np
from scipy.stats import zscore

x = np.array([10, 12, 11, 13, 12, 14, 11, 200])  # 200 là outlier rõ
z = zscore(x)
print(np.round(z, 2))
# [-0.39 -0.36 -0.37 -0.34 -0.36 -0.33 -0.37  2.52]

mask_outlier = np.abs(z) > 3
print(mask_outlier)
# Có thể tất cả False vì outlier ĐÃ kéo std lên cao,
# khiến chính nó không vượt ngưỡng 3.

Bẫy quan trọng của Z-score: outlier càng cực đoan càng kéo \( \mu \) và \( \sigma \) lên, dẫn đến chính nó có \( |z| \) nhỏ hơn ta nghĩ. Trong ví dụ trên, giá trị 200 chỉ có z ≈ 2.52 nên ngưỡng 3 không bắt được. Hai cách giảm bẫy:

  • Hạ ngưỡng (vd 2.5).
  • Dùng Modified Z-score với median và MAD (median absolute deviation) thay vì mean/std — robust hơn.

Z-score giả định data gần phân phối chuẩn. Với data lệch mạnh (income, page-view, file-size), giả định này sai và Z-score đánh giá thiếu chính xác — nên chuyển sang IQR.

6

IQR method

IQR dùng quartile — vị trí cắt 25% và 75% của data đã sort:

  • \( Q_1 \) = giá trị tại percentile 25 (1/4 sample nhỏ hơn nó).
  • \( Q_3 \) = giá trị tại percentile 75 (3/4 sample nhỏ hơn nó).
  • \( \text{IQR} = Q_3 - Q_1 \) — bề rộng của 50% trung tâm.

Một giá trị \( x \) bị coi là outlier nếu:

\[ x < Q_1 - 1.5 \times \text{IQR} \quad \text{hoặc} \quad x > Q_3 + 1.5 \times \text{IQR} \]

Hệ số 1.5 do Tukey (1977) đề xuất; có thể dùng 3.0 để chỉ bắt outlier "cực đoan". Hai ngưỡng \( Q_1 - 1.5\,\text{IQR} \) và \( Q_3 + 1.5\,\text{IQR} \) chính là whisker của box plot.

Vì \( Q_1, Q_3 \) là quartile, một vài outlier cực lớn không thay đổi \( \text{IQR} \) đáng kể — IQR robust với outlier, ngược với mean/std. Đó là lý do IQR ổn định hơn Z-score trên data skew.

Code Python:

import numpy as np

x = np.array([10, 12, 11, 13, 12, 14, 11, 200])

q1, q3 = np.percentile(x, [25, 75])
iqr = q3 - q1
lower = q1 - 1.5 * iqr
upper = q3 + 1.5 * iqr

mask_outlier = (x < lower) | (x > upper)
print(f"Q1={q1}, Q3={q3}, IQR={iqr}")
print(f"Bound: [{lower}, {upper}]")
print(f"Outlier mask: {mask_outlier}")
print(f"Outlier values: {x[mask_outlier]}")
# Q1=11.0, Q3=13.25, IQR=2.25
# Bound: [7.625, 16.625]
# Outlier mask: [F F F F F F F  T]
# Outlier values: [200]

So với Z-score ở mục trước (không bắt được 200), IQR phát hiện ngay.

7

So sánh Z-score vs IQR

Tiêu chíZ-scoreIQR
Thống kê dùngmean, stdquartile (median ngầm)
Giả định phân phốigần chuẩn (normal)không giả định
Robust với outlierkhông (bị chính outlier kéo lệch)
Robust với skewkhông
Tính nhanhrất nhanh, vector hoá tốtnhanh, cần sort hoặc np.percentile
Ngưỡng mặc định\( \|z\| > 3 \)1.5 × IQR
Visualize tự nhiênhistogrambox plot

Quy tắc thực dụng: data thực tế thường skew → mặc định dùng IQR. Chỉ chọn Z-score khi đã verify feature gần phân phối chuẩn (hoặc đã transform về gần chuẩn) và muốn ngưỡng theo "số std".

8

Visualize outlier

Trước khi quyết định xử lý, luôn nhìn data bằng plot. Ba dạng cơ bản:

  • Box plot — hiển thị \( Q_1, \) median, \( Q_3 \) thành "hộp", whisker kéo dài tới giá trị cuối trong khoảng \([Q_1 - 1.5\,\text{IQR},\ Q_3 + 1.5\,\text{IQR}]\), các điểm ngoài whisker được chấm rời rạc. Đọc nhanh được cả phân phối và outlier cùng lúc.
  • Histogram — bin tần suất. Outlier thể hiện thành "tail" rất dài ở một phía, hoặc các bin gần như rỗng ở rìa với một cột nhỏ tận cùng.
  • Scatter plot — hai feature trên hai trục, mỗi sample là 1 điểm. Outlier nằm tách rời cụm chính. Hữu ích cho multivariate outlier (mục 12).
import matplotlib.pyplot as plt
import numpy as np

x = np.concatenate([np.random.normal(50, 5, 200), [120, 130, 140]])

fig, axes = plt.subplots(1, 2, figsize=(10, 4))
axes[0].boxplot(x, vert=True)
axes[0].set_title("Box plot — chấm rời = outlier")

axes[1].hist(x, bins=30)
axes[1].set_title("Histogram — tail phải dài bất thường")
plt.show()

Trong EDA, vẽ box plot cho mọi feature số là bước rẻ nhất để phát hiện feature nào có outlier nặng và cần xử lý trước.

9

Chiến lược xử lý

Sau khi phát hiện, có 4 chiến lược chính:

  • Drop (loại bỏ) — xoá hẳn sample chứa outlier. An toàn nhất khi xác định đó là lỗi đo đạc và còn đủ sample. Nhược: giảm size dataset, có thể loại nhầm sample hợp lệ; nếu drop dựa trên feature \( X \) khi đã biết \( y \), có nguy cơ leak.
  • Cap / Winsorize — thay outlier bằng giá trị ngưỡng (\( Q_1 - 1.5\,\text{IQR} \) hoặc \( Q_3 + 1.5\,\text{IQR} \)), hoặc bằng giá trị tại percentile (vd cap ở percentile 1 và 99). Giữ nguyên số sample, chỉ làm "nhẹ" giá trị cực đoan. Hợp với linear model nhạy với extreme value.
  • Transform — dùng hàm phi tuyến nén tail: \( \log(1+x) \), \( \sqrt{x} \), Box-Cox, Yeo-Johnson. Outlier không bị loại mà bị "kéo gần" về cụm chính. Hợp với data dương lệch phải (income, file size). Bài 13 sẽ deep về transform.
  • Đổi sang model robust — không đụng đến data, chọn algorithm ít nhạy: RobustScaler (dùng median + IQR thay mean + std), Huber loss / Quantile loss thay MSE, tree-based / gradient boosting thay linear regression.

Trong cùng một dataset có thể kết hợp: cap những outlier nhẹ + drop những outlier do lỗi rõ ràng + chuyển model sang RobustScaler. Không có công thức chung — quyết định dựa vào lượng outlier, nguyên nhân, và sensitivity của model.

10

Khi nào KHÔNG drop outlier

  • Outlier là tín hiệu cần phát hiện. Trong fraud detection, các transaction gian lận chính là outlier về số tiền, tần suất, location. Drop outlier = drop chính cái mà model cần học.
  • Anomaly detection / quality control. Mục đích là tìm bất thường. Outlier là label, không phải nhiễu.
  • Dataset nhỏ. Vài chục sample mà drop 5% nghĩa là mất signal đáng kể. Cân nhắc cap hoặc transform thay vì drop.
  • Không chắc là lỗi hay sự kiện thật. Nếu không có cách verify với domain expert hay nguồn raw, drop có thể bóp méo phân phối. Mặc định: giữ và đổi model robust, hoặc cap nhẹ.
  • Outlier có pattern (cùng xuất hiện theo segment). Vd: tất cả outlier về thời gian phản hồi đều thuộc cùng một region — đây là tín hiệu segment, không phải nhiễu. Nên xử lý bằng feature mới (region indicator) thay vì xoá.
11

Workflow phát hiện outlier

  1. EDA bằng plot. Box plot + histogram cho mọi feature số. Ghi lại feature nào có outlier nhìn thấy được, mức độ ra sao.
  2. Định lượng. Tính số lượng outlier bằng IQR (mặc định) và/hoặc Z-score. Tỷ lệ < 1% rất khác > 10% — xử lý cũng khác.
  3. Cross-check với domain. Hỏi: giá trị này có hợp lý không? Có nguồn raw để verify không? Đo bằng đơn vị nào, có lỗi unit không?
  4. Quyết định chiến lược. Drop / cap / transform / đổi model — tuỳ nguyên nhân và task. Ghi lại quyết định để reproducible.
  5. Fit transform trên train, apply trên test. Mọi ngưỡng (\( Q_1, Q_3, \mu, \sigma \), cap value) phải tính trên train, dùng lại trên test/inference. Không tính lại trên test để tránh leak.

Document lại bước nào bạn drop, ngưỡng nào, lý do — outlier handling là phần thường bị "ẩn" trong code khiến kết quả không tái lập được.

12

Multivariate outlier

Z-score và IQR đều univariate — xét từng feature riêng. Nhưng một sample có thể bình thường trên mọi feature đơn lẻ nhưng bất thường khi xét tổ hợp. Vd: tuổi 25 (bình thường) + lương 50 triệu (bình thường) + chức danh "CEO" (bình thường) — nhưng tổ hợp 25 tuổi mà CEO ăn 50 triệu thì hiếm.

Một số công cụ phát hiện outlier theo nhiều chiều:

  • Mahalanobis distance. Đo khoảng cách của một điểm tới centroid của data, có hiệu chỉnh theo ma trận covariance \( \Sigma \): \[ D_M(\mathbf{x}) = \sqrt{(\mathbf{x} - \boldsymbol{\mu})^\top \Sigma^{-1} (\mathbf{x} - \boldsymbol{\mu})} \] Điểm có \( D_M \) lớn là outlier multivariate. Hợp với data gần Gaussian multivariate; cần ước lượng \( \Sigma \) ổn định nên cần đủ sample so với số chiều.
  • Isolation Forest. Model unsupervised — xây các random tree, đo xem mỗi sample bị "cô lập" sau bao nhiêu split. Outlier thường bị cô lập sớm (đường dẫn ngắn). Sklearn: sklearn.ensemble.IsolationForest. Không cần giả định phân phối, làm việc tốt với high-dimensional. Sẽ học chi tiết ở phần anomaly detection.
  • Local Outlier Factor (LOF). So sánh mật độ local của một điểm với mật độ các neighbor — điểm "lạc" giữa cụm khác bị đánh dấu. sklearn.neighbors.LocalOutlierFactor.
13

Use case AI/ML

  • Fraud detection (thẻ tín dụng, e-commerce). Outlier về số tiền, tần suất, vị trí địa lý chính là target. Pipeline thường giữ nguyên outlier ở feature, dùng model anomaly detection (Isolation Forest, autoencoder reconstruction error) hoặc supervised với class imbalance handling.
  • Sensor reading (IoT, manufacturing). Giá trị nhiệt độ, áp suất, rung động ngoài dải vật lý → lỗi sensor, drop/đánh dấu. Giá trị "lạ" trong dải vật lý → có thể là dấu hiệu hỏng hóc, giữ lại làm tín hiệu predictive maintenance.
  • Income / wealth data. Phân phối lương luôn skew phải, tail dài thực tế. Drop tỉ phú = bóp méo phân phối thật. Cap hoặc \( \log \) transform là lựa chọn thường gặp; với task dự đoán siêu giàu thì giữ nguyên.
  • Web analytics (session length, page views). Bot tạo session cực dài → outlier do lỗi. User power-user thực sự cũng tạo outlier dài nhưng hợp lệ. Cần segment trước khi quyết định.
14

Code Python end-to-end

Ráp cả Z-score, IQR, drop, và winsorize vào một feature:

import numpy as np
import pandas as pd
from scipy.stats import zscore
from scipy.stats.mstats import winsorize

df = pd.DataFrame({
    "salary": [30, 32, 35, 33, 40, 38, 36, 42, 1000, 1500]
})

# --- 1) Z-score ---
z = zscore(df["salary"])
df["is_outlier_z"] = np.abs(z) > 3
print(df[["salary", "is_outlier_z"]])

# --- 2) IQR ---
q1, q3 = df["salary"].quantile([0.25, 0.75])
iqr = q3 - q1
lower, upper = q1 - 1.5 * iqr, q3 + 1.5 * iqr
df["is_outlier_iqr"] = (df["salary"] < lower) | (df["salary"] > upper)
print(f"IQR bound: [{lower}, {upper}]")

# --- 3) Drop bằng IQR ---
df_clean = df[~df["is_outlier_iqr"]].copy()
print(f"Sau drop: {len(df_clean)} / {len(df)} sample")

# --- 4) Cap (Winsorize) — cắt 10% mỗi phía ---
salary_capped = winsorize(df["salary"], limits=[0.0, 0.2])
df["salary_capped"] = np.asarray(salary_capped)
print(df[["salary", "salary_capped"]])

# --- 5) Cap thủ công theo ngưỡng IQR ---
df["salary_iqr_capped"] = df["salary"].clip(lower=lower, upper=upper)
print(df[["salary", "salary_iqr_capped"]])

Vài lưu ý:

  • winsorize(limits=[a, b]) cắt \( a \) phần trăm thấp nhất và \( b \) phần trăm cao nhất bằng giá trị percentile tương ứng. limits=[0.0, 0.2] nghĩa là không cắt đáy, cắt 20% đỉnh.
  • pd.Series.clip(lower, upper) cap về ngưỡng cụ thể — tiện khi muốn dùng đúng \( Q_1 - 1.5\,\text{IQR} \) và \( Q_3 + 1.5\,\text{IQR} \).
  • Khi áp pipeline train/test: tính \( q_1, q_3, \mu, \sigma \) trên train, lưu lại, apply trên test với cùng giá trị. Không gọi winsorize riêng trên test (sẽ tính percentile mới → leak ngầm).
15

Bài tập thực hành

Bài 1. Cho salary = [30, 35, 40, 45, 50, 1000]. Tính:

  • Z-score của từng giá trị. Với ngưỡng \( |z| > 3 \), có giá trị nào bị flag không? Giải thích vì sao kết quả "trái với cảm nhận".
  • \( Q_1, Q_3, \text{IQR} \), ngưỡng dưới và trên theo IQR (hệ số 1.5). Liệt kê outlier.
  • So sánh số lượng outlier mà mỗi method phát hiện.

Bài 2. Với cùng list ở Bài 1, cap outlier bằng ngưỡng \( Q_3 + 1.5 \times \text{IQR} \) (dùng Series.clip). In list mới và tính lại mean trước/sau cap — quan sát mức thay đổi.

Bài 3. Cho dataset fraud detection có cột transaction_amount. 0.5% transaction có amount > 1000 lần median. Trả lời:

  • Có nên drop các transaction outlier này trước khi train model phân loại fraud / non-fraud không? Vì sao?
  • Đề xuất 2 cách xử lý thay thế để giữ tín hiệu nhưng vẫn giảm tác động của outlier lên scaling.

Gợi ý đáp án Bài 1. Với salary = [30, 35, 40, 45, 50, 1000]: mean ≈ 200, std ≈ 357. Z-score của 1000 ≈ (1000 - 200)/357 ≈ 2.24 → KHÔNG vượt 3, Z-score không bắt được vì chính nó kéo \( \mu, \sigma \) lên. IQR: \( Q_1 = 36.25,\ Q_3 = 48.75,\ \text{IQR} = 12.5 \), upper = 67.5 → 1000 bị flag rõ. IQR ổn định hơn vì quartile không bị một giá trị cực kéo lệch.

Gợi ý đáp án Bài 3. KHÔNG drop. Outlier về amount chính là feature quan trọng để phân biệt fraud — drop là tự xoá tín hiệu. Hai cách thay thế: (a) dùng RobustScaler (median + IQR) khi scale, giữ nguyên giá trị nhưng giảm ảnh hưởng lên model linear; (b) log1p(amount) để nén tail, giữ thứ tự và sự khác biệt nhưng đưa giá trị về dải dễ học hơn cho NN / logistic regression.

16

Bài tiếp theo

Bài 13: Feature Engineering cơ bản — tạo feature mới từ feature gốc: log/sqrt/Box-Cox transform để nén tail và giảm tác động outlier, binning, interaction feature, datetime feature, polynomial expansion, và quy tắc khi nào nên/không nên engineer.