Как найти выбросы в данных

Что такое выброс

Выброс (outlier) — значение, которое сильно отличается от остальных в выборке.

Примеры:

  • Средний чек 1000₽, один заказ на 500 000₽ — выброс.
  • Возраст пользователей 18–80, один 200 — выброс (ошибка).
  • Температура 20°C, внезапно 50°C — сенсор сломался.

Зачем их находить

  1. Корректировать метрики. Один выброс двигает среднее, искажает AOV, ARPU.
  2. Обнаруживать баги. Возраст 200 или транзакция за год до регистрации — ошибка в данных.
  3. Антифрод. Подозрительно большая покупка — возможно мошенничество.
  4. ML-модели. Outliers ломают линейные модели.

Методы обнаружения

1. Z-score (для нормального распределения)

from scipy import stats
df['z'] = stats.zscore(df['amount'])
outliers = df[df['z'].abs() > 3]

Строки с z > 3 (3σ от среднего) — выбросы.

Когда работает: данные распределены нормально.

Когда не работает: скошенные распределения (доход, LTV, время сессии).

2. IQR (универсальный)

q1 = df['amount'].quantile(0.25)
q3 = df['amount'].quantile(0.75)
iqr = q3 - q1
lower = q1 - 1.5 * iqr
upper = q3 + 1.5 * iqr
outliers = df[(df['amount'] < lower) | (df['amount'] > upper)]

Стандартный метод boxplot. Устойчив к скосу.

Visualize:

import seaborn as sns
sns.boxplot(data=df, x='amount')

Точки вне «усов» — outliers.

3. Перцентили

lower = df['amount'].quantile(0.01)
upper = df['amount'].quantile(0.99)
outliers = df[(df['amount'] < lower) | (df['amount'] > upper)]

Отсекаем 1% экстремумов с каждой стороны — простой метод.

4. Isolation Forest (ML)

from sklearn.ensemble import IsolationForest

iso = IsolationForest(contamination=0.01, random_state=42)
df['anomaly'] = iso.fit_predict(df[['amount', 'items_count']])
outliers = df[df['anomaly'] == -1]

Работает с многомерными данными. Contamination = ожидаемая доля выбросов.

5. DBSCAN (clustering-based)

from sklearn.cluster import DBSCAN

db = DBSCAN(eps=0.5, min_samples=5)
df['cluster'] = db.fit_predict(df[['x', 'y']])
outliers = df[df['cluster'] == -1]

Точки, не попавшие в кластеры — выбросы.

6. Визуально — scatter plot

import seaborn as sns
sns.scatterplot(data=df, x='price', y='amount')

Точки далеко от общего облака — выбросы.

Тренироваться на таких вопросах можно в Telegram-боте Карьерник — там 1500+ задач с реальных собесов с разборами.

В SQL

IQR в SQL

WITH bounds AS (
    SELECT
        PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY amount) AS q1,
        PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY amount) AS q3
    FROM orders
)
SELECT o.*
FROM orders o, bounds b
WHERE o.amount < b.q1 - 1.5 * (b.q3 - b.q1)
   OR o.amount > b.q3 + 1.5 * (b.q3 - b.q1);

Z-score в SQL

WITH stats AS (
    SELECT AVG(amount) AS mu, STDDEV(amount) AS sd FROM orders
)
SELECT o.*,
    (o.amount - s.mu) / s.sd AS z_score
FROM orders o, stats s
WHERE ABS((o.amount - s.mu) / s.sd) > 3;

Что делать с выбросами

1. Удалить

df_clean = df[~df.index.isin(outliers.index)]

Подходит, если:

  • Это ошибка в данных.
  • Выбросов мало.
  • Выбросы не важны для анализа.

2. Обрезать (winsorize)

from scipy.stats import mstats
df['amount_clipped'] = mstats.winsorize(df['amount'], limits=[0.01, 0.01])

Значения > p99 становятся p99. Сохраняем строки, убираем влияние.

3. Заменить

median = df['amount'].median()
df.loc[outlier_mask, 'amount'] = median

Компромисс: строка остаётся, но значение «исправлено».

4. Логарифмировать

df['log_amount'] = np.log1p(df['amount'])

Для скошенных распределений — логарифм делает их ближе к нормальному, выбросы становятся менее влиятельными.

5. Анализировать отдельно

Иногда выбросы — это важная группа («киты» в LTV, фрод-транзакции). Не удалять, а изучать отдельно.

Как решать, что делать

Удалять, если

  • Ошибка данных (возраст 200).
  • Выбросов < 1% и они мешают анализу.
  • Для ML обучения линейных моделей.

Оставить, если

  • Выбросы — реальные данные (киты-пользователи).
  • В медиане и p95 сохраняется смысл.
  • Анализ робастный к выбросам.

Логарифмировать, если

  • LTV, revenue, income — скошенные распределения.
  • Хотите сохранить всю информацию.
  • Строите regression модель.

К слову, набить руку на таких кейсах удобно через тренажёр в Telegram — разбирайте по 10 вопросов в день, через 2 недели тема становится рефлексом.

Типичные ошибки

1. Удалять выбросы без проверки

«Выбросы» могут быть настоящими power-users, приносящими 80% revenue. Удалите — разрушите бизнес-логику.

2. Использовать среднее + std для скошенных данных

Mean и std сами искажены выбросами. Используйте median и IQR.

3. Игнорировать многомерность

Точка может быть нормальной по каждой колонке, но аномальной в паре. Используйте Isolation Forest.

4. Не визуализировать

Всегда сначала boxplot / scatter — глаз лучше любого алгоритма.

Читайте также

FAQ

Z-score или IQR?

IQR для скошенных распределений (обычная ситуация). Z-score для нормальных.

Isolation Forest сложный?

Нет. 3 строки кода через sklearn. Хорошо работает из коробки.

Всегда удалять выбросы перед анализом?

Нет. Сначала понять, что они значат. В некоторых бизнесах outliers — это главное.

Как быть с выбросами в A/B-тесте?

Либо тримминг/winsorization (обрезка p99), либо Mann-Whitney U test (устойчив к выбросам), либо CUPED.