Перейти к содержанию

252 уроков, 17 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 10 из 20 Средний 30 мин 130 XP

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()
Вывод
График отображается под редактором
Первый запуск загружает pandas и seaborn — это займёт до минуты

Пять элементов, и каждый — точное число из данных. Медиана делит замеры пополам: половина заказов быстрее неё, половина медленнее. Ящик между квартилями огораживает середину — давайте сведём элементы в таблицу, чтобы дальше говорить одним языком:

ЭлементЧто этоКак считается
Линия в ящикеМедиана — середина данныхПоловина наблюдений ниже, половина выше
ЯщикМежквартильный размах 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))
Проверь себя
0 / 5

1. Что означает линия внутри ящика boxplot?

2. Где заканчиваются усы ящика?

3. Точка за пределами уса на boxplot — это что?

4. Что добавит hue="shift" в sns.boxplot(data=df, x="district", y="minutes")?

5. Почему boxplot, построенный по трём наблюдениям, ненадёжен?

Карточки терминов
Запомнено: 0 / 6
Практика

В редакторе — время ответа поддержки в час пик и вне пик. Постройте boxplot по дням недели с отображением среднего (showmeans=True) и заголовком «Время ответа по дням».

practice.py
Вопросы и ответы по уроку

Как читать ящик с усами на 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-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.