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, регрессии.

Проверьте себя
1. Какая функция Seaborn умеет фасетинг (сетку панелей по col/row)?
Ascatterplot
Bboxplot
Ccatplot (figure-level)
Dhistplot
2. Какую функцию можно встроить в свою сетку plt.subplots через ax=?
Arelplot
Bdisplot
Cscatterplot (axes-level)
Dcatplot