Figure-level и axes-level функции
Главная путаница новичков в Seaborn: почему одни функции рисуют сетку графиков, а другие — только один. Разберём раз и навсегда.
«Figure-level управляет всей фигурой и умеет фасетинг; axes-level — это аккуратная замена одной команде Matplotlib».
Функции Seaborn делятся на два уровня. Axes-level (scatterplot, histplot, boxplot) рисуют в одну Axes и ведут себя как drop-in замена Matplotlib — их можно класть в свои subplots. Figure-level (relplot, displot, catplot) управляют всей фигурой, сами создают сетку и умеют фасетинг — разбивку на панели по категориям.
Почему это различие так важно на практике? От выбора уровня зависит, кто владеет фигурой. Когда вы строите сложный отчёт, где рядом должны стоять разнородные графики — слева boxplot, справа карта корреляций, снизу временной ряд, — вам нужна полная власть над раскладкой через plt.subplots, и здесь работают только axes-level функции: вы передаёте каждой свою ax=. А когда задача обратная — показать один вид графика, но в разрезе нескольких категорий (продажи по регионам, распределение по группам пациентов), — ручная раскладка превращается в десятки строк, и тут figure-level функция строит всю сетку одной командой. То есть это не вопрос вкуса, а вопрос того, однородны панели или нет.
Есть и более тонкое следствие: возвращаемый объект у двух уровней разный, и это меняет, как вы дальше настраиваете график. Axes-level возвращает обычную Axes Matplotlib, и вы донастраиваете её привычными методами (ax.set_title, ax.set_ylim). Figure-level возвращает FacetGrid — обёртку над всей сеткой со своими методами: g.set_axis_labels(), g.set_titles(), g.add_legend(), а до отдельных осей нужно добираться через g.axes или g.axes_dict. Новички часто зовут на FacetGrid методы Axes и получают ошибку — важно держать в голове, какого типа объект у вас в руках.
import seaborn as sns
# axes-level: рисует в конкретную ax
fig, ax = plt.subplots()
sns.boxplot(data=df, x="day", y="tip", ax=ax)
# figure-level: сам управляет фигурой + фасетинг по col=
sns.catplot(data=df, x="day", y="tip", kind="box",
col="smoker", row="time") # сетка панелей!
# три figure-level "зонтика":
# relplot -> scatterplot / lineplot
# displot -> histplot / kdeplot / ecdfplot
# catplot -> boxplot / violinplot / barplot / stripplot
SEABORN: два уровня функций
figure-level (FacetGrid) axes-level (одна Axes)
relplot scatterplot / lineplot
displot histplot / kdeplot
catplot boxplot / violin / barplot
| |
управляет всей фигурой рисует в переданную ax
умеет col= row= (фасетинг) встраивается в subplots
возвращает FacetGrid возвращает Axes
Как работает под капотом
Figure-level функции строят FacetGrid: они сами создают сетку Axes по параметрам col и row, раскладывают данные по панелям и синхронизируют оси. Поэтому у них есть уникальные параметры height, aspect, col_wrap. Axes-level рисуют в одну переданную ax и ничего за её пределами не трогают — потому встраиваются в ваши собственные plt.subplots.
Фасетинг — это группировка строк по комбинациям категорий. Сделаем такую разбивку вручную.
# Фасетинг вручную: раскладываем наблюдения по панелям (day x smoker)
rows = [
("Fri", "yes", 2.0), ("Fri", "no", 3.5), ("Sat", "yes", 4.0),
("Sat", "no", 3.0), ("Sat", "yes", 5.0), ("Fri", "no", 2.5),
]
panels = {}
for day, smoker, tip in rows:
panels.setdefault((day, smoker), []).append(tip)
for (day, smoker), tips in sorted(panels.items()):
avg = sum(tips) / len(tips)
print("панель day={:<3} smoker={:<3} | n={} | среднее={:.2f}".format(
day, smoker, len(tips), avg))
«Попробуй сам ▶» — каждая «панель» здесь соответствует одному subplot в сетке FacetGrid, который строит catplot(col=, row=).
Частые ошибки
Передавать ax= в figure-level функцию — она его не принимает (только axes-level). Пытаться сделать фасетинг axes-level функцией — она не умеет, нужен figure-level. Удивляться, что figure-level игнорирует ваши plt.subplots. Путать relplot (фигура) и scatterplot (ось).
Ещё несколько типичных промахов. Менять размер figure-level графика через plt.figure(figsize=...) — это не работает, потому что фигуру создаёт сам FacetGrid; размер задаётся параметрами height (высота одной панели в дюймах) и aspect (отношение ширины к высоте), а не привычным figsize. Дальше — звать plt.title() для общего заголовка сетки: он сядет только на последнюю ось; общий заголовок над всей сеткой ставят через g.figure.suptitle(). И наоборот: пытаться встроить вывод figure-level функции внутрь чужого subplot — у вас уже есть отдельная самостоятельная фигура, вложить её нельзя, нужна axes-level функция с ax=.
Отдельная ловушка — десятки панелей. Если в колонке для col сотни уникальных значений, catplot честно нарисует сотни крошечных нечитаемых панелей и съест всю память. Перед фасетингом всегда оценивайте число категорий и при необходимости ограничивайте сетку через col_wrap (перенос на новую строку после N панелей) или предварительно агрегируйте данные.
Best practices
- Нужен фасетинг (col/row) — берите figure-level (relplot/displot/catplot).
- Встраиваете в свою сетку subplots — берите axes-level с
ax=. - Запомните три «зонтика»: relplot, displot, catplot и их kind=.
Итог: вы различаете уровни Seaborn. Дальше — мощные статграфики: heatmap, pairplot, регрессии.