Как выявить и обработать выбросы (outliers) в данных?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Как выявить и обработать выбросы (outliers) в данных?
Выброс (outlier) — это значение, которое значительно отличается от остальных данных в наборе. Выбросы могут возникать из-за ошибок измерения, опечаток в данных, или редких, но легальных событий. Для Data Analyst правильно выявлять и обрабатывать выбросы критично, так как они могут исказить анализ и привести к неправильным выводам.
Почему выбросы проблематичны:
- Искажение статистики — выбросы увеличивают среднее, дисперсию, стандартное отклонение
- Влияние на модели — регрессионные модели очень чувствительны к выбросам
- Неправильные выводы — выбросы могут привести к ложным выводам о данных
- Инфляция показателей — средние и суммы становятся неправдоподобными
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# Пример: данные с выбросом
data = [100, 105, 102, 103, 104, 101, 102, 99, 101, 5000] # 5000 — выброс!
print(f'Среднее без анализа: {np.mean(data):.2f}') # 551.7 (неправильно!)
print(f'Медиана без анализа: {np.median(data):.2f}') # 101.5 (правильно)
print(f'Стандартное отклонение: {np.std(data):.2f}') # 1577 (очень завышено)
Метод 1: IQR (Interquartile Range)
Это самый популярный метод, основанный на квартилях.
Формула:
- Q1 — первый квартиль (25-й процентиль)
- Q3 — третий квартиль (75-й процентиль)
- IQR = Q3 - Q1
- Выброс если: X < Q1 - 1.5×IQR или X > Q3 + 1.5×IQR
import numpy as np
import pandas as pd
# Данные
data = pd.Series([100, 105, 102, 103, 104, 101, 102, 99, 101, 5000])
# Рассчитать квартили
Q1 = data.quantile(0.25)
Q3 = data.quantile(0.75)
IQR = Q3 - Q1
print(f'Q1: {Q1}')
print(f'Q3: {Q3}')
print(f'IQR: {IQR}')
# Определить границы
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
print(f'Нижняя граница: {lower_bound}')
print(f'Верхняя граница: {upper_bound}')
# Найти выбросы
outliers = data[(data < lower_bound) | (data > upper_bound)]
print(f'Выбросы: {outliers.values}')
# Или использовать встроенный метод pandas
print(f'\nБез выбросов:')
data_clean = data[(data >= lower_bound) & (data <= upper_bound)]
print(data_clean.values)
Метод 2: Z-score (стандартное отклонение)
Базируется на расстоянии в стандартных отклонениях от среднего.
Формула:
z = (x - mean) / std
Обычно считаем выбросом если |z| > 3 (или > 2.5 для более строгого критерия).
from scipy import stats
import numpy as np
data = np.array([100, 105, 102, 103, 104, 101, 102, 99, 101, 5000])
# Рассчитать z-score
z_scores = np.abs(stats.zscore(data))
print(f'Z-scores: {z_scores}')
# Найти выбросы (|z| > 3)
threshold = 3
outlier_mask = z_scores > threshold
outliers = data[outlier_mask]
print(f'Выбросы (|z| > 3): {outliers}')
print(f'Индексы выбросов: {np.where(outlier_mask)[0]}')
# Более мягкий критерий: |z| > 2.5
threshold = 2.5
outlier_mask = z_scores > threshold
outliers = data[outlier_mask]
print(f'\nВыбросы (|z| > 2.5): {outliers}')
Метод 3: Метод изоляции (Isolation Forest)
Машинное обучение подход для обнаружения выбросов в многомерных данных.
from sklearn.ensemble import IsolationForest
import numpy as np
import pandas as pd
# Многомерные данные
X = pd.DataFrame({
'age': [25, 30, 28, 32, 29, 26, 31, 100, 27, 29], # 100 — возможный выброс
'income': [50000, 60000, 55000, 65000, 58000, 52000, 62000, 55000, 59000, 61000]
})
# Обучить модель (contamination — предполагаемая доля выбросов)
iso_forest = IsolationForest(contamination=0.1, random_state=42)
predictions = iso_forest.fit_predict(X)
# -1 означает выброс, 1 — нормальное значение
X['is_outlier'] = predictions
print(X[X['is_outlier'] == -1])
Метод 4: MAD (Median Absolute Deviation)
Робустный метод, менее чувствительный к экстремальным выбросам.
import numpy as np
from scipy.stats import median_abs_deviation
data = np.array([100, 105, 102, 103, 104, 101, 102, 99, 101, 5000])
# Рассчитать MAD
median = np.median(data)
mad = median_abs_deviation(data)
print(f'Медиана: {median}')
print(f'MAD: {mad}')
# Выброс если: |x - median| > 3 * MAD
threshold = 3
outlier_mask = np.abs(data - median) > threshold * mad
outliers = data[outlier_mask]
print(f'Выбросы: {outliers}')
Обработка выбросов:
После выявления нужно решить, что с ними делать.
Вариант 1: Удаление
Просто исключить выбросы из анализа (если они явно ошибки).
import pandas as pd
df = pd.DataFrame({
'sales': [100, 105, 102, 103, 104, 101, 102, 99, 101, 5000],
'date': pd.date_range('2024-01-01', periods=10)
})
# Удалить выбросы (IQR метод)
Q1 = df['sales'].quantile(0.25)
Q3 = df['sales'].quantile(0.75)
IQR = Q3 - Q1
df_clean = df[(df['sales'] >= Q1 - 1.5*IQR) & (df['sales'] <= Q3 + 1.5*IQR)]
print(df_clean)
Вариант 2: Трансформация
Применить логарифм или корень для сжатия диапазона значений.
import numpy as np
data = np.array([100, 105, 102, 103, 104, 101, 102, 99, 101, 5000])
# Логарифмическая трансформация
log_data = np.log(data)
print(f'Оригинальные данные: {data}')
print(f'После log: {log_data}')
# Квадратный корень
sqrt_data = np.sqrt(data)
print(f'После sqrt: {sqrt_data}')
Вариант 3: Winsorization
Заменить экстремальные значения на граничные.
from scipy.stats.mstats import winsorize
import numpy as np
data = np.array([100, 105, 102, 103, 104, 101, 102, 99, 101, 5000])
# Winsorize: заменить top/bottom 10% на 90th/10th процентили
winsorized = winsorize(data, limits=0.1)
print(f'Оригинальные: {data}')
print(f'Winsorized: {winsorized}')
Вариант 4: Заполнение медианой/средним
Заменить выбросы на статистику (медиану или среднее).
import pandas as pd
import numpy as np
df = pd.DataFrame({'value': [100, 105, 102, 103, 104, 101, 102, 99, 101, 5000]})
# Найти выбросы
Q1 = df['value'].quantile(0.25)
Q3 = df['value'].quantile(0.75)
IQR = Q3 - Q1
outlier_mask = (df['value'] < Q1 - 1.5*IQR) | (df['value'] > Q3 + 1.5*IQR)
# Заменить на медиану
median = df['value'].median()
df.loc[outlier_mask, 'value'] = median
print(df)
Практический пример: Анализ продаж
import pandas as pd
import numpy as np
from scipy import stats
# Данные о суточных продажах
df = pd.DataFrame({
'date': pd.date_range('2024-01-01', periods=100),
'sales': np.random.normal(1000, 100, 100) # Нормальные данные
})
# Добавить выбросы (выходные с низкими продажами)
df.loc[[10, 50, 75], 'sales'] = [200, 150, 180]
# Добавить выброс (день распродажи с высокими продажами)
df.loc[30, 'sales'] = 5000
print(f'Среднее с выбросами: {df["sales"].mean():.2f}')
print(f'Медиана: {df["sales"].median():.2f}')
# Обнаружить выбросы
Q1 = df['sales'].quantile(0.25)
Q3 = df['sales'].quantile(0.75)
IQR = Q3 - Q1
outlier_mask = (df['sales'] < Q1 - 1.5*IQR) | (df['sales'] > Q3 + 1.5*IQR)
outliers = df[outlier_mask]
print(f'\nНайдено выбросов: {len(outliers)}')
print(outliers)
# Анализ: естественный ли выброс?
for idx, row in outliers.iterrows():
if row['sales'] < Q1 - 1.5*IQR:
print(f"Дата {row['date'].date()}: низкие продажи (возможно выходной)")
else:
print(f"Дата {row['date'].date()}: высокие продажи (возможно специальное событие)")
# Очищенные данные
df_clean = df[~outlier_mask]
print(f'\nСреднее после удаления выбросов: {df_clean["sales"].mean():.2f}')
Чек-лист для Data Analyst:
✅ Визуализируй данные (box plot, histogram, scatter plot) — выбросы видны глазом ✅ Используй IQR для быстрого обнаружения ✅ Используй Z-score для нормально распределённых данных ✅ Используй Isolation Forest для многомерных данных ✅ ВСЕГДА исследуй выброс перед удалением — может быть важный сигнал ✅ Документируй решение: что ты делал с выбросами и почему ✅ Повтори анализ с/без выбросов — посмотри, как они влияют на результаты ✅ Не удаляй слепо — оцени бизнес-контекст
Выводы:
- IQR — самый простой и надёжный метод
- Z-score — хорош для нормальных распределений
- Isolation Forest — лучше для многомерных данных
- Всегда исследуй выброс перед обработкой
- Не удаляй автоматически — выброс может быть бизнес-информацией
- Документируй свои решения по обработке выбросов
Правильная работа с выбросами — это балансирование между чистотой данных и потерей информации.