Как посчитать MAD outliers в SQL

Закрепи формулу mad outliers в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать mad outliers в Telegram

Зачем MAD

MAD (Median Absolute Deviation) — robust альтернатива стандартному отклонению. Не страдает от выбросов: 50% точек могут быть «плохими», MAD всё равно покажет правильный спред. Идеальный детектор для рядов с frequent outliers — ошибки трекинга, manual data entry, события brand spikes.

Формула

MAD = median(|x_i − median(x)|)
modified_z_i = 0.6745 × (x_i − median(x)) / MAD

0.6745 — constant Φ⁻¹(0.75), приводящая MAD к эквиваленту σ для normal distribution. С этим коэффициентом modified z-score сравним с обычным z (порог 3 — стандарт).

MAD в SQL

В Postgres PERCENTILE_CONT(0.5) даёт median. MAD считается двумя проходами:

WITH median_calc AS (
    SELECT
        PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY value) AS med
    FROM api_latency
    WHERE event_time >= CURRENT_DATE - INTERVAL '7 days'
),
abs_deviations AS (
    SELECT
        a.event_time,
        a.value,
        ABS(a.value - m.med) AS abs_dev,
        m.med
    FROM api_latency a
    CROSS JOIN median_calc m
    WHERE a.event_time >= CURRENT_DATE - INTERVAL '7 days'
),
mad_calc AS (
    SELECT
        PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY abs_dev) AS mad,
        MAX(med) AS med
    FROM abs_deviations
)
SELECT
    a.event_time,
    a.value,
    m.med,
    m.mad,
    0.6745 * (a.value - m.med) / NULLIF(m.mad, 0) AS modified_z
FROM abs_deviations a
CROSS JOIN mad_calc m
WHERE ABS(0.6745 * (a.value - m.med) / NULLIF(m.mad, 0)) > 3
ORDER BY ABS(a.value - m.med) DESC
LIMIT 50;

|modified_z| > 3 — стандартный порог, эквивалентный |z| > 3 для нормального распределения.

Modified z-score

Удобно вынести в CASE:

WITH ... -- median и mad, как выше
labelled AS (
    SELECT
        event_time,
        value,
        0.6745 * (value - med) / NULLIF(mad, 0) AS modified_z
    FROM abs_deviations
    CROSS JOIN mad_calc
)
SELECT
    event_time,
    value,
    modified_z,
    CASE
        WHEN ABS(modified_z) > 3.5 THEN 'outlier'
        WHEN ABS(modified_z) > 2.5 THEN 'borderline'
        ELSE 'normal'
    END AS verdict
FROM labelled;
Закрепи формулу mad outliers в Карьернике
Запомнить надолго — 5 коротких сессий с задачами на эту тему. Бесплатно
Тренировать mad outliers в Telegram

Rolling MAD

Для time-series — rolling median и rolling MAD:

WITH rolling AS (
    SELECT
        event_date,
        value,
        -- rolling median: для каждой точки берём окно
        PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY value) OVER w AS rolling_med
    FROM daily_metric
    WINDOW w AS (
        ORDER BY event_date
        ROWS BETWEEN 28 PRECEDING AND 1 PRECEDING
    )
)
SELECT
    event_date,
    value,
    rolling_med,
    ABS(value - rolling_med) AS abs_dev
FROM rolling;

Postgres 14+ позволяет PERCENTILE_CONT(...) OVER w. В более старых — материализация двумя проходами.

Частые ошибки

Ошибка 1. MAD = 0. Если >50% точек одинаковые — MAD = 0, деление ломается. Возьмите среднюю MAD или σ как backup.

Ошибка 2. Забыть 0.6745. Без поправки modified-z не сравнима с обычным z-score. Пороги другие.

Ошибка 3. MAD на multi-modal. Mode «free» и «paid» — median окажется между ними. MAD будет огромен, мало outliers.

Ошибка 4. Применять MAD к категориальным. MAD только для ordinal/continuous.

Ошибка 5. Сравнение с z на normal distribution. На нормали MAD = 0.6745 × σ — поправка нужна. На skewed данных MAD «свой».

Связанные темы

FAQ

MAD vs IQR?

Оба robust. MAD строже (использует median, не Q1/Q3). IQR проще объяснять.

Modified z или просто MAD?

Modified z — для сравнения с z-score эталоном. Просто MAD — как value сам по себе.

Если >50% дубли?

MAD = 0. Используйте IQR или z-score с робастной σ.

Какой порог?

|modified_z| > 3.5 — стандарт у Iglewicz & Hoaglin.

MAD для time-series?

Rolling median + rolling MAD — самый robust способ для streaming-данных.