Grubbs, Dixon, and Generalized ESD: Turning Outlier Detection into a Hypothesis Test
Classical statistical hypothesis tests for outlier detection: Grubbs test (single outlier), Dixon Q-test (small samples), Generalized ESD (multiple outliers) — p-values, formulas, scipy implementation, and when to use which.
Şükrü Yusuf KAYA
26 min read
Intermediate🧪 Heuristikten hipoteze
Önceki iki derste 'z-score 3'ten büyükse outlier' veya 'IQR'ın 1.5 katı dışındaysa outlier' gibi eşik tabanlı kararlar kullandık. Bu derste resmi istatistiksel hipotez testlerine geçeceğiz: 'bu gözlem outlier mı, p-değeri nedir?' Compliance ağır sektörler (banking, healthcare, regulatory) bu seviyede formal kararlar bekler.
Hipotez Testi Çerçevesi#
Bir outlier testi temelde şu cümleyi soruyor:
H0 (null hipotez): "Tüm gözlemler aynı normal dağılımdan geliyor (outlier yok)." H1 (alternatif hipotez): "En az bir gözlem farklı bir dağılımdan geliyor (outlier var)."
Test istatistiği hesaplanır → kritik değer veya p-değeri ile karşılaştırılır
→ H0 reddedilir veya reddedilemez.
Önemli not: Hipotez testleri "outlier ne kadar büyük" sorusunu cevaplamaz;
"outlier istatistiksel anlamlı mı" sorusunu cevaplar. Production'da bu önemli
bir ayrım.
Burada (\bar{x}) = mean, s = standart sapma. G büyükse outlier var.
Kritik Değer#
Verilen anlamlılık α (genelde 0.05) ve örneklem boyutu n için kritik değer:
Eğer G > G_crit ise H0 reddedilir → "outlier var" (en aşırı uçtaki gözlem outlier).
Tek-yönlü vs İki-yönlü#
- İki-yönlü (yukarıda): en aşırı sapan (max veya min) outlier mı sorusu
- Tek-yönlü: sadece "max outlier mı" veya "min outlier mı"
Çoklu outlier — Iterative Grubbs#
Grubbs aslında tek outlier testidir. Birden fazla varsa:
- Grubbs uygula → en aşırı outlier'ı bul
- Outlier'ı veriden çıkar
- Tekrar Grubbs uygula
- H0 reddedilemez olunca dur
Uyarı: Iterative Grubbs çoklu test düzeltmesi yapmaz; bu nedenle ESD daha tercih edilir (aşağıda).
python
import numpy as npfrom scipy import stats def grubbs_test(x, alpha=0.05): """ Grubbs test — tek outlier için (two-sided). Returns: is_outlier_present: True/False outlier_index: int or None G: test istatistiği G_critical: kritik değer """ x = np.asarray(x, dtype=float) n = len(x) if n < 3: raise ValueError("Grubbs için en az 3 gözlem gerek") mu = np.mean(x) s = np.std(x, ddof=1) # sample std abs_dev = np.abs(x - mu) max_idx = np.argmax(abs_dev) G = abs_dev[max_idx] / s # Kritik değer (two-sided) t_val = stats.t.ppf(1 - alpha / (2 * n), n - 2) G_crit = ((n - 1) / np.sqrt(n)) * np.sqrt( t_val**2 / (n - 2 + t_val**2) ) is_outlier = G > G_crit return { 'is_outlier_present': is_outlier, 'outlier_index': int(max_idx) if is_outlier else None, 'outlier_value': float(x[max_idx]) if is_outlier else None, 'G': float(G), 'G_critical': float(G_crit), } # Örneknp.random.seed(42)data = np.random.normal(loc=50, scale=5, size=30)data = np.append(data, 100) # bir outlier ekle result = grubbs_test(data, alpha=0.05)print(f"Outlier var mı: {result['is_outlier_present']}")print(f"G = {result['G']:.3f}, G_crit = {result['G_critical']:.3f}")print(f"Outlier değer: {result['outlier_value']}")Grubbs test implementasyonu
Sıralı veriden (x_{(1)} \le x_{(2)} \le \cdots \le x_{(n)}):
- En küçük gözlemin outlier olup olmadığını test: (Q_{10} = \frac{x_{(2)} - x_{(1)}}{x_{(n)} - x_{(1)}})
- En büyük gözlemin outlier olup olmadığını test: (Q_{10} = \frac{x_{(n)} - x_{(n-1)}}{x_{(n)} - x_{(1)}})
Daha büyük n için farklı formüller var (Q11, Q21, Q22). scipy doğrudan
desteklemiyor; paketi veya kendi implementasyonun.
outliersKritik Değer Tablosu (α=0.05, n=3-10)#
| n | Q_crit (α=0.05) |
|---|---|
| 3 | 0.941 |
| 4 | 0.765 |
| 5 | 0.642 |
| 6 | 0.560 |
| 7 | 0.507 |
| 8 | 0.468 |
| 9 | 0.437 |
| 10 | 0.412 |
Q > Q_crit ise outlier.
Kullanım Senaryosu#
Dixon özellikle:
- Laboratuvar deneyleri (5-7 tekrarlı ölçüm)
- Kalite kontrol (10 örneklik partiler)
- Analitik kimya (ASTM standartlarında zorunlu)
Büyük veride Dixon yerine Grubbs veya ESD tercih edilir.
python
def dixon_q_test(x, alpha=0.05): """ Dixon Q-test (Q10) — small sample outlier test. Sadece n=3-7 için lookup table dahili; n=8-30 için kabaca extend ettim. """ Q_TABLE = { 3: 0.941, 4: 0.765, 5: 0.642, 6: 0.560, 7: 0.507, 8: 0.468, 9: 0.437, 10: 0.412, 15: 0.338, 20: 0.300, 25: 0.277, 30: 0.260, } x_sorted = np.sort(x) n = len(x_sorted) if n < 3 or n > 30: raise ValueError("Dixon Q-test için 3 <= n <= 30 önerilir") q_lower = (x_sorted[1] - x_sorted[0]) / (x_sorted[-1] - x_sorted[0]) q_upper = (x_sorted[-1] - x_sorted[-2]) / (x_sorted[-1] - x_sorted[0]) # En yakın tabloya hangisi n'e karşılık? closest_n = min(Q_TABLE.keys(), key=lambda k: abs(k - n)) q_crit = Q_TABLE[closest_n] return { 'q_lower': float(q_lower), 'q_upper': float(q_upper), 'q_critical': float(q_crit), 'lower_outlier': q_lower > q_crit, 'upper_outlier': q_upper > q_crit, 'lower_value': float(x_sorted[0]) if q_lower > q_crit else None, 'upper_value': float(x_sorted[-1]) if q_upper > q_crit else None, } # Kimya lab örneği — 7 ölçümölçümler = [9.95, 10.01, 10.03, 10.05, 10.07, 10.10, 11.50]result = dixon_q_test(ölçümler)print(f"Q (üst): {result['q_upper']:.3f}, Q_crit: {result['q_critical']:.3f}")print(f"Üst outlier: {result['upper_outlier']}")print(f"Outlier değer: {result['upper_value']}")Dixon Q-test — küçük örneklem outlier testi
Generalized ESD (Extreme Studentized Deviate)#
Bernard Rosner, 1983. Grubbs'un çoklu outlier'a sağlam uzantısı.
Avantajı#
Grubbs'u iteratif uygulamak çoklu test problemine yol açar (her testte α
hatası birikir). ESD bu problemi çözer:
- Önceden maximum outlier sayısı (r) belirtirsin
- Test r kez koşar
- Her adımda farklı kritik değer kullanır (çoklu test düzeltmesi içerir)
Algoritma#
INPUT: data x, max outlier sayısı r, anlamlılık α FOR k = 1 to r: 1. Mean ve std hesapla 2. R_k = max |x_i - mean| / std (eski Grubbs G) 3. En büyük R_k olan gözlemi işaretle ve veriden çıkar 4. Kritik değer λ_k hesapla (Rosner formülü) 5. R_k > λ_k ise bu gözlem outlier END FOR
Kritik değer formülü:
python
def generalized_esd(x, max_outliers=10, alpha=0.05): """ Generalized ESD — multiple outliers in normal data. """ x = np.asarray(x, dtype=float).copy() n_init = len(x) outlier_indices = [] # orijinal indislerle original_indices = np.arange(n_init) R_values = [] lambda_values = [] for k in range(1, max_outliers + 1): if len(x) < 3: break mu = np.mean(x) s = np.std(x, ddof=1) abs_dev = np.abs(x - mu) max_idx_local = np.argmax(abs_dev) R_k = abs_dev[max_idx_local] / s n = len(x) p = 1 - alpha / (2 * (n - k + 1)) t_val = stats.t.ppf(p, n - k - 1) lambda_k = ((n - k) * t_val) / np.sqrt( (n - k - 1 + t_val**2) * (n - k + 1) ) R_values.append(R_k) lambda_values.append(lambda_k) if R_k > lambda_k: # Bu gözlem outlier — orijinal indis ekle outlier_indices.append(int(original_indices[max_idx_local])) # Bu gözlemi çıkar mask = np.arange(len(x)) != max_idx_local x = x[mask] original_indices = original_indices[mask] return { 'outlier_indices': outlier_indices, 'R_values': R_values, 'lambda_values': lambda_values, 'n_outliers': len(outlier_indices), } # Örneknp.random.seed(42)data = np.concatenate([ np.random.normal(50, 5, 100), [80, 90, 100, 15, 10], # 5 outlier]) result = generalized_esd(data, max_outliers=10, alpha=0.05)print(f"Toplam outlier: {result['n_outliers']}")print(f"Outlier indisleri: {result['outlier_indices']}")print(f"Outlier değerler: {data[result['outlier_indices']]}")Generalized ESD — multi-outlier test
Üç Test Yan Yana#
| Özellik | Grubbs | Dixon Q | Generalized ESD |
|---|---|---|---|
| Outlier sayısı | 1 (iteratif ile çok) | 1 | Çoklu (öncesi belirli) |
| Örneklem boyutu | n ≥ 3 | 3 ≤ n ≤ ~30 | n ≥ 10 |
| Dağılım varsayımı | Normal | Normal | Normal |
| Çoklu test düzeltmesi | Yok (yanlış) | Yok | Var |
| Tipik kullanım | Tek outlier şüphesi | Lab/kalite kontrol | Production AD |
| Hesaplama | Hızlı | Çok hızlı | Orta |
| Python | scipy + custom | custom | custom veya pyod.models.gmm |
Pratik Karar#
- n < 30 ve tek outlier şüphesi: Dixon Q
- n ≥ 30 ve tek outlier şüphesi: Grubbs
- Birden fazla outlier mümkün: ESD (max sayıyı tahmin et)
- Normal dağılım varsayımı bozulmuş: Önce log/Box-Cox transform; ya da non-parametric yöntemlere (IQR, percentile) dön
P-Değer Kullanırken Dikkat#
Hipotez testleri p-değer üretir. Bunu üretimde kullanırken:
Tuzak 1: Çoklu Test Cehennemi#
1000 feature üzerinde tek tek Grubbs uygularsan, α=0.05 ile yaklaşık 50
false positive beklersin. Bonferroni veya Benjamini-Hochberg ile düzelt.
Tuzak 2: P-Hacking#
Birkaç farklı eşik dene, en düşük p-değeri ver. Yasaktır. Test öncesi α'yı sabitle.
Tuzak 3: P-Değer ≠ Outlier "Büyüklüğü"#
P=0.001 outlier, p=0.01 outlier'dan 10 kat daha büyük değildir. P-değer sadece "yanlış pozitif riski" söyler.
Tuzak 4: Anlamlı ≠ Önemli#
İstatistiksel anlamlı bir outlier, iş açısından önemli olmayabilir. Bir transaction p=0.001 ile outlier ama tutar 50 TL ise compliance açısından önemsiz olabilir.
📜 Compliance ipucu
BDDK ve EBA denetimlerinde 'modelimiz outlier tespit ediyor' demek yetmez; istatistiksel test ile desteklemen istenebilir. Bu durumda Grubbs veya ESD'nin formal raporu (test istatistiği, kritik değer, p-değer) bir compliance artefakt olarak değerlidir. Modül 30'da (Explainable AD) bu tür dokümantasyonu detaylı işleyeceğiz.
Modern AD'de Hipotez Testleri Hala Geçerli mi?#
Kısa cevap: evet, ama sınırlı.
Hala değerli yerleri#
- Compliance / regulatory raporlama
- Küçük örneklem (lab, kalite kontrol)
- Tek-feature tarama (örn. her sensör için bağımsız test)
- Modern ML modellerinin bypass edemediği baseline
Sınırları#
- Yüksek-boyut zayıflığı: çoklu test cehennemi
- Non-normal dağılım problemleri
- Bağlam-bağımlılığı yok: müşteri profili gibi covariate'leri kullanamaz
- Adversarial: attacker testin matematiğini öğrenip atlatabilir
Modern AD pipeline'ında klasik testler genellikle birinci geçit: hızlı,
yorumlanabilir, ucuz. ML modelleri ikinci geçit: derin, bağlam-bağımlı,
güçlü ama yavaş ve daha az yorumlanabilir.
👉 Bir sonraki ders
Ders 2.4 — Chebyshev, EVT, POT. Normal varsayımı geçerli olmayan, uç kuyruklu (Pareto-tail) verilerde outlier modellemek için Extreme Value Theory (EVT) ve Peak Over Threshold (POT) yöntemleri. Banking, network, telekom datasında baş aktör.
Frequently Asked Questions
Doğrudan yok, ama `pyod.models.gmm` veya `outliers` PyPI paketi var. Çoğu data scientist Grubbs'u 30 satırda kendi yazıyor (yukarıdaki gibi). scipy.stats.t kritik değeri verir, geri kalan formüller direkt.
Yorumlar & Soru-Cevap
(0)Yorum yazmak için giriş yap.
Yorumlar yükleniyor...
Related Content
Module 0: Course Framework & Workshop Setup
Who Is an Anomaly Detection Engineer? Differences from Fraud, SRE, Quality Engineer Roles and the Turkey Salary Landscape
Start LearningModule 0: Course Framework & Workshop Setup
Course Philosophy: Why This Path, Why This Order — The Anomaly Detection Learning River
Start LearningModule 0: Course Framework & Workshop Setup