boxplot в Seaborn: как читать ящик с усами и сравнивать группы
Один символ — и вся форма распределения перед глазами: медиана, квартили, разброс и выбросы. Учимся читать ящик с усами, сравнивать группы и добавлять hue.
Редакция Питоники
Среднее — обманчивая сводка: два района доставки со средней «25 минут» могут быть совершенно разными — в одном все заказы приходят за 23–27 минут, в другом то 15, то 45. Гистограмма из четвёртого урока показала бы форму каждого района, но на пять районов понадобится пять графиков. boxplot («ящик с усами») решает иначе: сжимает каждое распределение в один компактный символ и ставит все группы рядом на общую шкалу.
Разберём анатомию ящика, посчитаем его части руками в pandas, поймём правило усов и выбросов, а в конце добавим hue — второй разрез внутри каждого ящика.
Анатомия ящика с усами
Сквозной пример — время доставки пиццы по двум районам: центр и окраина, по шесть замеров на каждый. Строим первый boxplot и разбираем, что на нём нарисовано:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
delivery = pd.DataFrame({
"district": ["Центр"] * 6 + ["Окраина"] * 6,
"minutes": [20, 22, 25, 19, 24, 27, 18, 22, 35, 38, 45, 90],
})
sns.set_theme()
ax = sns.boxplot(data=delivery, x="district", y="minutes")
ax.set_title("Время доставки: центр и окраина")
ax.set_xlabel("Район")
ax.set_ylabel("Минут")
plt.show()
График отображается под редактором
Пять элементов, и каждый — точное число из данных. Медиана делит замеры пополам: половина заказов быстрее неё, половина медленнее. Ящик между квартилями огораживает середину — давайте сведём элементы в таблицу, чтобы дальше говорить одним языком:
| Элемент | Что это | Как считается |
|---|---|---|
| Линия в ящике | Медиана — середина данных | Половина наблюдений ниже, половина выше |
| Ящик | Межквартильный размах IQR | От Q1 (25-й процентиль) до Q3 (75-й) |
| Усы | Разброс без выбросов | До 1.5 × IQR от краёв ящика |
| Точки за усами | Выбросы | Значения дальше 1.5 × IQR от ящика |
| Высота и ширина | Разброс данных | Широкий ящик — данные рассеяны, узкий — плотные |
Правильнее читать по порядку: медиана — типичный заказ, ящик — где живут средние 50% заказов, усы — нормальный разброс, точки — исключения. Проверим центральный район руками в pandas — ящик обязан совпасть с числами:
import pandas as pd
delivery = pd.DataFrame({
"district": ["Центр"] * 6 + ["Окраина"] * 6,
"minutes": [20, 22, 25, 19, 24, 27, 18, 22, 35, 38, 45, 90],
})
center = delivery.loc[delivery["district"] == "Центр", "minutes"]
q1, median, q3 = center.quantile([0.25, 0.5, 0.75])
print(q1, median, q3)
20.5 23.0 24.75
Совпало с картинкой: нижний край ящика — 20.5 минуты, медиана — 23, верхний — 24.75. Ящик центрального района узкий: средние 50% заказов укладываются в четыре минуты. Разброс — это и есть главная информация, которой нет в среднем.
Усы и выбросы: правило полутора IQR
Усы тянутся от краёв ящика на полтора межквартильных размаха, но не дальше самого крайнего значения в этих границах. Посчитаем границу для окраины — и сравним с её максимумом:
import pandas as pd
delivery = pd.DataFrame({
"district": ["Центр"] * 6 + ["Окраина"] * 6,
"minutes": [20, 22, 25, 19, 24, 27, 18, 22, 35, 38, 45, 90],
})
outskirt = delivery.loc[delivery["district"] == "Окраина", "minutes"]
q1, q3 = outskirt.quantile(0.25), outskirt.quantile(0.75)
iqr = q3 - q1
whisker_top = q3 + 1.5 * iqr
print(whisker_top)
print(outskirt.max())
70.25 90
Граница усов — 70.25 минуты, а максимум равен 90. Значит, заказ на 90 минут вылетел за ус и на графике он нарисован отдельной точкой-выбросом. Это не ошибка данных: сначала стоит проверить, была ли пробка или ураган, и только потом решать, что с записью делать.
Сравнение групп: почему ящики рядом
Главный трюк boxplot — общая шкала Y. Медиана окраины почти вдвое выше центральной, ящик в разы шире, плюс выброс: район отличается не только «в среднем медленнее», но и «непредсказуемее». Для логистики это два разных вывода — первый про плановое время, второй про надёжность обещаний. Один взгляд на два ящика заменил сравнение медиан, квартилей и минимумов вручную.
hue в boxplot: два разреза сразу
Параметр hue= из девятого урока добавляет в каждый район вторую группу — например, дневную и вечернюю смену. Получаются пары ящиков: районы по оси X, смена цветом:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
delivery = pd.DataFrame({
"district": ["Центр"] * 6 + ["Окраина"] * 6,
"shift": ["день", "день", "день", "вечер", "вечер", "вечер"] * 2,
"minutes": [20, 22, 24, 25, 27, 31, 30, 34, 38, 40, 45, 52],
})
sns.set_theme()
ax = sns.boxplot(data=delivery, x="district", y="minutes", hue="shift")
ax.set_title("Время доставки: район и смена")
ax.set_xlabel("Район")
ax.set_ylabel("Минут")
plt.show()
График отображается под редактором
Теперь виден второй слой вывода: в центре день и вечер почти не различаются, а на окраине вечерняя смена стабильно медленнее. Три ящика «центр-день», «центр-вечер», «окраина-день», «окраина-вечер» — четыре сравнения из одной строки кода.
Полезные опции: среднее и горизонтальный ящик
В ящике по умолчанию показана только медиана. Среднее арифметическое включается флагом showmeans=True — полезно, когда медиана и среднее расходятся (это признак перекошенных данных):
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
delivery = pd.DataFrame({
"district": ["Центр"] * 6 + ["Окраина"] * 6,
"minutes": [20, 22, 25, 19, 24, 27, 18, 22, 35, 38, 45, 90],
})
sns.set_theme()
ax = sns.boxplot(data=delivery, x="district", y="minutes", showmeans=True,
meanprops={"marker": "D", "markerfacecolor": "white"})
ax.set_title("Ромб — среднее, линия — медиана")
ax.set_xlabel("Район")
ax.set_ylabel("Минут")
plt.show()
График отображается под редактором
На окраине ромб-среднее уехал вверх относительно медианы — выброс в 90 минут потянул среднее за собой. Медиана осталась на месте: ещё один довод доверять ей при перекошенных данных. Если подписи категорий длинные, ящик удобно повернуть: поменяйте местами x= и y=, и ящики лягут горизонтально.
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
delivery = pd.DataFrame({
"district": ["Центр"] * 6 + ["Окраина"] * 6,
"minutes": [20, 22, 25, 19, 24, 27, 18, 22, 35, 38, 45, 90],
})
sns.set_theme()
ax = sns.boxplot(data=delivery, x="minutes", y="district")
ax.set_title("Горизонтальные ящики: подписи дышат свободно")
ax.set_xlabel("Минут")
plt.show()
График отображается под редактором
Данные не изменились — повернулась только картинка. Горизонтальная ориентация выручает в дашбордах, где по вертикали уже десять подписей, и в отчётах с длинными названиями категорий вроде названий магазинов или моделей.
Когда boxplot не подходит
Ящик сжимает данные так сильно, что форма теряется: два распределения с одинаковыми квартилями — одно симметричное, второе с двумя горбами — дадут неразличимые ящики. Если подозреваете несколько мод или хотите показать «где масса», посмотрите гистограмму или следующий инструмент курса — violinplot, который рисует форму распределения целиком. Для первого взгляда на группы boxplot остаётся самым быстрым способом: один символ на группу, вся статистика внутри.
Что дальше
Вы научились читать ящик: медиана, IQR, правило полутора IQR для усов, выбросы-точки; сравнивать группы на общей шкале и добавлять второй разрез через hue. Следующий урок раскрывает то, что ящик прячет: violinplot рисует форму распределения целиком — где масса, где хвосты и как разрезать скрипку пополам параметром split.
Усы ящика — не минимум и не максимум: за их границами живут выбросы, которые важно рассмотреть, а не удалить сгоряча.
Сначала предскажи ответ в голове — это главный навык программиста.
import pandas as pd
s = pd.Series([12, 15, 9, 21, 14])
print(s.median())
import pandas as pd
s = pd.Series([1, 2, 3, 4, 100])
print(s.quantile(0.5))
import pandas as pd
s = pd.Series([10, 12, 14, 16, 18, 30])
q1, q3 = s.quantile(0.25), s.quantile(0.75)
print(round(q3 - q1, 1))
1. Что означает линия внутри ящика boxplot?
2. Где заканчиваются усы ящика?
3. Точка за пределами уса на boxplot — это что?
4. Что добавит hue="shift" в sns.boxplot(data=df, x="district", y="minutes")?
5. Почему boxplot, построенный по трём наблюдениям, ненадёжен?
В редакторе — время ответа поддержки в час пик и вне пик. Постройте boxplot по дням недели с отображением среднего (showmeans=True) и заголовком «Время ответа по дням».
Как читать ящик с усами на boxplot?
Линия в ящике — медиана, сам ящик — межквартильный размах от Q1 до Q3 (средние 50% данных), усы тянутся на 1.5 × IQR от краёв, точки за усами — выбросы. Широкий ящик — большой разброс, узкий — плотные данные около медианы.
Урок 10: boxplot — ящик с усамиЯвляются ли концы усов минимумом и максимумом?
Обычно нет. Усы заканчиваются на расстоянии 1.5 × IQR от краёв ящика, и значения за границей рисуются отдельными точками-выбросами. Минимум и максимум совпадают с усами только в выборках без выбросов.
Чем boxplot отличается от гистограммы?
Гистограмма показывает форму распределения целиком — горбы, провалы, скошенность. Boxplot сжимает данные до пяти чисел (медиана, два квартиля, две границы усов) и выигрывает при сравнении многих групп на одной шкале. Для формы берите histplot или violinplot, для сравнения групп — boxplot.
Как показать среднее на boxplot в Seaborn?
Флагом showmeans=True — среднее появится отдельным маркером. Расхождение маркера с линией медианы сигнализирует о перекошенном распределении: выбросы тянут среднее, а медиана остаётся на месте.
Понравился урок? Сошлитесь на него
«Усы ящика — не минимум и не максимум: за их границами живут выбросы, которые важно рассмотреть, а не удалить сгоряча.»
Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.
Что читать дальше
Seaborn · Урок 9
hue в Seaborn: цвет как третье измерение графика
hue разбивает график по третьей колонке таблицы: точки, столбцы и линии раскрашиваются по группам, а легенда строится сама. Разбираем scatterplot, barplot, lineplot и hue_order.
Seaborn · Урок 11
violinplot в Seaborn: форма распределения вместо ящика
Скрипка показывает распределение целиком: где масса наблюдений, где хвосты и есть ли второй горб. Сравниваем с boxplot, режем скрипку параметром split.
Похожие уроки по темам
Подобраны автоматически по пересечению тем и ключевых слов.
Matplotlib / Seaborn · Урок 8
Гистограмма и ящик с усами в Matplotlib: hist и boxplot
plt.hist и plt.boxplot на одних данных: бины и density, чтение формы распределения, медиана, квартили, усы 1.5 × IQR и выбросы — плюс сравнение двух групп обоими способами. Весь код запускается на странице.
matplotlib boxplotсравнение распределений python
pytest · Урок 10
Числа с плавающей точкой: pytest.approx
0.1 + 0.2 в Python не равно 0.3 — и обычный assert на float падает непонятно почему. pytest.approx сравнивает числа с допуском и возвращает тестам спокойный сон.
pytest.approx float сравнениеpytest сравнение float
Seaborn · Урок 14
jointplot в Seaborn: связь двух величин и распределения по краям
jointplot рисует связь двух величин в центре и их распределения по краям: сгущения и выбросы видны одновременно. kind="kde" и kind="hex" для плотных данных.
seaborn jointplotjointgrid seaborn