«Ящик с усами», он же боксплот или диаграмма размаха — одна из самых полезных, но и самых непонятных для новичков визуализаций в анализе данных. На первый взгляд, это просто странный прямоугольник с линиями. На самом деле, он дает исчерпывающее представление о распределении данных, помогает находить выбросы и сравнивать группы между собой.
В этом руководстве мы разберем анатомию боксплота от и до. Вы не просто научитесь его «читать», но и поймете, какая математика стоит за каждой его частью, и, конечно, научитесь строить и интерпретировать такие графики на Python с помощью matplotlib
, seaborn
и pandas
.
Представьте, что у вас есть набор данных. Вы можете посчитать среднее, медиану или моду, но этих мер центральной тенденции часто недостаточно. Нужно понимать, насколько данные изменчивы, как они разбросаны, есть ли в них аномалии.
Боксплот — это стандартизированный способ визуализировать распределение на основе сводки из пяти ключевых чисел:
Он наглядно показывает симметричность данных, их плотность, наличие перекосов и выбросов. В сравнении с гистограммой или графиком плотности, боксплот может показаться менее интуитивным, но он несет в себе гораздо больше концентрированной информации.
Давайте препарируем наш «ящик» и разберем каждую его часть
Q3 - Q1
. В этом диапазоне лежит центральная половина (50%) всех ваших данных. IQR — это ключевая мера разброса, устойчивая к выбросам.[!INFO] Квартили делят ваши данные на четыре равные части. Процентили делят их на 100 частей. Таким образом, Q1 — это 25-й процентиль, медиана (Q2) — 50-й, а Q3 — 75-й.
Теперь разберемся с «усами» и точками за их пределами.
Q3 + 1.5 * IQR
. Это верхняя граница «нормального» диапазона данных.Q1 - 1.5 * IQR
. Это нижняя граница «нормального» диапазона.Чтобы глубже понять суть боксплота, полезно посмотреть, как он выглядит для идеального, эталонного случая — нормального распределения.
На рисунке выше показано, как части боксплота соответствуют областям под кривой плотности вероятности (Probability Density Function, PDF) для нормального распределения.
Не пугайтесь, сейчас все станет просто. PDF — это функция, которая описывает вероятность того, что случайная величина примет определенное значение. Для непрерывных данных (как в нашем случае), вероятность попадания в конкретный диапазон равна площади под кривой PDF в этом диапазоне.
Для стандартного нормального распределения (где среднее μ = 0
и стандартное отклонение σ = 1
), формула упрощается.
Давайте построим этот знаменитый «колокол» с помощью Python.
# Импортируем необходимые библиотеки
from scipy.integrate import quad
import numpy as np
import matplotlib.pyplot as plt
# Генерируем данные для оси X
x = np.linspace(-4, 4, num = 100)
# Рассчитываем PDF для стандартного нормального распределения
constant = 1.0 / np.sqrt(2 * np.pi)
pdf_normal_distribution = constant * np.exp((-x**2) / 2.0)
# Строим график
fig, ax = plt.subplots(figsize=(10, 5))
ax.plot(x, pdf_normal_distribution)
ax.set_ylim(0)
ax.set_title('График плотности вероятности нормального распределения', size = 16)
ax.set_ylabel('Плотность вероятности', size = 14)
plt.show()
Теперь самое интересное. Мы можем посчитать, какая доля данных лежит в межквартильном диапазоне. Для нормального распределения Q1 и Q3 находятся на расстоянии примерно 0.6745
стандартных отклонений от среднего. Чтобы найти вероятность, нам нужно взять интеграл (то есть посчитать площадь под кривой) от -0.6745
до 0.6745
.
К счастью, Python сделает это за нас.
# Функция, описывающая нашу кривую
def normal_probability_density(x):
constant = 1.0 / np.sqrt(2 * np.pi)
return(constant * np.exp((-x**2) / 2.0))
# Считаем интеграл для межквартильного размаха
# quad возвращает результат и оценку погрешности, нам нужен только результат
iqr_prob, _ = quad(normal_probability_density, -0.6745, 0.6745, limit = 1000)
print(f"Вероятность попадания в межквартильный размах: {iqr_prob:.4f}")
# Вывод: Вероятность попадания в межквартильный размах: 0.5000
Результат — 0.5
, что идеально соответствует определению IQR, который содержит 50% данных.
Аналогично, границы «усов» для нормального распределения (±2.698
стандартных отклонений) охватывают примерно 99.3%
данных. Оставшиеся 0.7%
— это и есть теоретические выбросы.
[!TIP] Ключевой вывод: Боксплот — это не просто рисунок. Его структура математически связана с распределением вероятностей. Понимание этой связи позволяет извлекать из графика максимум информации.
Теперь, когда мы буквально по косточкам разобрали теоретическую часть с помощью Python, перейдем к практике.
Теория — это хорошо, но в реальной жизни данные редко бывают идеально нормальными. Давайте применим наши знания на настоящем датасете. Мы будем использовать распространённый "Висконсинский диагностический набор данных по раку молочной железы".
Наша задача — с помощью боксплотов проанализировать взаимосвязь между категориальным признаком (диагноз: злокачественная или доброкачественная опухоль) и непрерывным признаком (например, средняя площадь опухоли area_mean
).
Первым делом загрузим данные в DataFrame.
import pandas as pd
from sklearn.datasets import load_breast_cancer
data = load_breast_cancer()
df = pd.DataFrame(data.data, columns=data.feature_names)
df['diagnosis'] = data.target
print("\nРаспределение по диагнозам:")
print(df['diagnosis'].value_counts())
Распределение по диагнозам:
diagnosis
1 357
0 212
Name: count, dtype: int64
В Python есть много библиотек для визуализации. Рассмотрим три самых популярных способа построить боксплот для сравнения групп.
Seaborn
— это высокоуровневая библиотека, которая позволяет строить красивые статистические графики с минимальными усилиями. Это, пожалуй, самый простой и рекомендуемый способ для подобных задач.
import seaborn as sns
plt.figure(figsize=(8, 7))
sns.boxplot(x='diagnosis', y='mean area', data=df)
plt.title('Распределение площади опухоли по диагнозам (Seaborn)', fontsize=16)
plt.xlabel('Диагноз (1: Доброкачественная, 0: Злокачественная)', fontsize=12)
plt.ylabel('Средняя площадь опухоли (mean area)', fontsize=12)
plt.show()
Matplotlib
— это базовая библиотека, которая лежит в основе seaborn
. Она дает абсолютный контроль над каждым элементом графика, но требует чуть больше кода.
import matplotlib.pyplot as plt
# Сначала нужно подготовить данные: разделить их на две выборки
malignant = df[df['diagnosis'] == 0]['mean area']
benign = df[df['diagnosis'] == 1]['mean area']
fig, ax = plt.subplots(figsize=(8, 7))
# Matplotlib принимает данные в виде списка списков/массивов
ax.boxplot([malignant, benign], labels=['Malignant', 'Benign'])
ax.set_title('Распределение площади опухоли по диагнозам (Matplotlib)', fontsize=16)
ax.set_ylabel('Средняя площадь опухоли (mean area)', fontsize=12)
plt.show()
У pandas
есть свои встроенные методы для быстрой визуализации, которые являются оберткой над matplotlib
. Это удобно для экспресс-анализа прямо из DataFrame.
fig, ax = plt.subplots(figsize=(8, 7))
df.boxplot(column='mean area', by='diagnosis', ax=ax)
# Pandas создает свой заголовок, который лучше заменить или убрать
plt.suptitle('') # Убираем автоматический заголовок
plt.title('Распределение площади опухоли по диагнозам (Pandas)', fontsize=16)
plt.xlabel('Диагноз', fontsize=12)
plt.ylabel('Средняя площадь опухоли (mean area)', fontsize=12)
plt.show()
Иногда вы хотите не просто посмотреть на распределения, а сравнить их медианы с определенной долей уверенности. Для этого существуют боксплоты с «насечками» (notch
). Эти насечки представляют собой доверительный интервал (по умолчанию 95%) вокруг медианы.
[!TIP] Простое правило: если насечки двух боксплотов не пересекаются, это является статистически значимым свидетельством того, что их медианы действительно различаются.
# Используем код для Matplotlib, добавив один параметр: notch=True
malignant = df[df['diagnosis'] == 0]['mean area']
benign = df[df['diagnosis'] == 1]['mean area']
fig, ax = plt.subplots(figsize=(8, 7))
ax.boxplot([malignant, benign], labels=['Malignant', 'Benign'], notch=True) # Вот и вся магия
ax.set_title('Сравнение медиан с помощью "насечек"', fontsize=16)
ax.set_ylabel('Средняя площадь опухоли (area_mean)', fontsize=12)
plt.show()
Ваша поддержка — это энергия для новых статей и проектов. Спасибо, что читаете!
Давайте посмотрим на последний график и сделаем выводы, которые можно получить буквально за несколько секунд, взглянув на боксплот.
area_mean
для злокачественных опухолей (Malignant
) значительно выше, чем для доброкачественных (Benign
). Это логично и ожидаемо. График с насечками подтверждает, что это различие статистически значимо (насечки не пересекаются).Malignant
есть несколько верхних выбросов. Это пациенты с аномально большой площадью опухоли, которые могут требовать отдельного изучения. В группе Benign
выбросов меньше, что указывает на более стабильные и предсказуемые размеры доброкачественных новообразований.[!NOTE] Важно помнить, что
matplotlib
и другие библиотеки вычисляют квартили и границы усов непосредственно из данных, а не из какой-то теоретической модели распределения. Поэтому внешний вид вашего боксплота всегда будет зависеть от конкретной выборки, ее размера и реального распределения.