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

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

Начать обучение
Урок 18 из 20 Средний 25 мин 120 XP

FacetGrid: малые кратные — сравнить группы рядом

Три города на одной гистограмме — каша, три гистограммы рядом — отчёт. FacetGrid: одна формула графика, сетка панелей по любой колонке таблицы.

Редакция Питоники

Задача из жизни аналитика: в интернет-магазин пришло три потока чеков — из Москвы, Казани и Сочи — и руководителю нужно понять, чем отличаются покупатели городов. Первый инстинкт — наложить всё на один график и раскрасить по городам. Получится каша. Правильный приём придумал задолго до pandas: Эдвард Тюфте назвал его малыми кратными — каждая группа получает свою маленькую панель, и сравнение происходит взглядом слева направо. В Seaborn за это отвечает FacetGrid.

Проблема: три распределения на одном графике

Данные урока — 15 чеков, по пять на город. Суммы в рублях: Москва заметно дороже, Сочи дешевле, но с одним крупным заказом. Сначала посмотрим, как эту задачу решают «в лоб»:

каша: три города одной заливкой
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd

df = pd.DataFrame({
    "город": ["Москва", "Москва", "Москва", "Москва", "Москва",
              "Казань", "Казань", "Казань", "Казань", "Казань",
              "Сочи", "Сочи", "Сочи", "Сочи", "Сочи"],
    "чек": [1240, 830, 1520, 990, 1110,
            620, 540, 780, 690, 610,
            350, 980, 470, 390, 420],
})

sns.histplot(data=df, x="чек", hue="город", bins=12)
plt.show()
Вывод
График отображается под редактором

Формально всё правильно: histplot из урока про распределения, hue раскрасил города. На практике читать невозможно: заливки налегают друг на друга, столбики мешают друг другу, а редкий сочинский чек растягивает ось так, что казанская куча сжимается в узкую полоску. Сводка числами подтверждает, что разница есть, но не показывает её форму:

сводка по городам: разница видна в цифрах
import pandas as pd

df = pd.DataFrame({
    "город": ["Москва", "Москва", "Москва", "Москва", "Москва",
              "Казань", "Казань", "Казань", "Казань", "Казань",
              "Сочи", "Сочи", "Сочи", "Сочи", "Сочи"],
    "чек": [1240, 830, 1520, 990, 1110,
            620, 540, 780, 690, 610,
            350, 980, 470, 390, 420],
})

summary = df.groupby("город")["чек"].agg(["count", "mean", "max"]).round(0)
print(summary)
Вывод
        count    mean   max
город                      
Казань      5   648.0   780
Москва      5  1138.0  1520
Сочи        5   522.0   980

FacetGrid: одна формула — много панелей

FacetGrid работает в два шага. Первый: g = sns.FacetGrid(df, col="город") — контейнер создаёт сетку, по одной панели на значение колонки «город». Второй: g.map(функция, "колонка") — функция рисуется в каждую панель, причём ей отдаётся только подмножество строк своей группы. Формула графика пишется один раз, а применяется столько раз, сколько панелей:

FacetGrid: по гистограмме на город
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd

df = pd.DataFrame({
    "город": ["Москва", "Москва", "Москва", "Москва", "Москва",
              "Казань", "Казань", "Казань", "Казань", "Казань",
              "Сочи", "Сочи", "Сочи", "Сочи", "Сочи"],
    "чек": [1240, 830, 1520, 990, 1110,
            620, 540, 780, 690, 610,
            350, 980, 470, 390, 420],
})

g = sns.FacetGrid(df, col="город", height=3)
g.map(sns.histplot, "чек", binwidth=200)
g.set_axis_labels("Сумма чека, руб.", "Число чеков")
g.set_titles("город: {col_name}")
plt.show()
Вывод
График отображается под редактором

Три одинаковые оси — и сравнение честное: Москва стоит на 800-1500, Казань компактно в районе 600-800, у Сочи основная масса до 500 плюс одиночный чек под тысячу. Обратите внимание на binwidth=200: одинаковая ширина корзин на всех панелях делает столбики сопоставимыми, а set_titles вставляет значение категории в заголовок через шаблон {col_name}.

map и map_dataframe

g.map принимает любую функцию первого аргументом — не только sns-функции. Правило простое: map вызывает её как функция(подмножество_по_оси, ...) и подходит для готовых seaborn-графиков. Для собственной функции удобнее g.map_dataframe: она передаёт в функцию целое подмножество таблицы как data, и внутри доступны все колонки. Текущую панель функция берёт через plt.gca() — «get current axes». Добавим на каждую панель вертикальную линию среднего:

map_dataframe: своя функция со средней
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd

df = pd.DataFrame({
    "город": ["Москва", "Москва", "Москва", "Москва", "Москва",
              "Казань", "Казань", "Казань", "Казань", "Казань",
              "Сочи", "Сочи", "Сочи", "Сочи", "Сочи"],
    "чек": [1240, 830, 1520, 990, 1110,
            620, 540, 780, 690, 610,
            350, 980, 470, 390, 420],
})

def add_mean(data, **kws):
    ax = plt.gca()
    ax.axvline(data["чек"].mean(), color="crimson", linestyle="--", linewidth=2)

g = sns.FacetGrid(df, col="город", height=3)
g.map_dataframe(sns.histplot, x="чек", binwidth=200)
g.map_dataframe(add_mean)
g.set_axis_labels("Сумма чека, руб.", "Число чеков")
plt.show()
Вывод
График отображается под редактором

Каждая панель получила пунктир на своём среднем: 1138 у Москвы, 648 у Казани, 522 у Сочи. map_dataframe вызывает add_mean отдельно для каждого подмножества строк — именно поэтому data["чек"].mean() внутри каждый раз считает свою цифру, а не общую.

Внутри панелей работает и hue: каждая панель раскрашивается по своей группе, а общую легенду собирает g.add_legend(). Так сравнивают уже два разреза — города рядом, каналы цветом:

hue внутри фасетов и общая легенда
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd

df = pd.DataFrame({
    "город": (["Москва"] * 6) + (["Казань"] * 6),
    "канал": (["онлайн", "магазин"] * 6),
    "чек": [1240, 830, 1520, 990, 1110, 700,
            620, 540, 780, 690, 610, 650],
})

g = sns.FacetGrid(df, col="город", hue="канал", height=3)
g.map(sns.histplot, "чек", binwidth=200, alpha=0.6)
g.add_legend()
g.set_axis_labels("Сумма чека, руб.", "Число чеков")
plt.show()
Вывод
График отображается под редактором

row, col_wrap и размер панелей

Параметр row добавляет второе измерение сетки: FacetGrid(df, col="город", row="канал") даст панель на каждую пару значений город × канал. Размер каждой панели, как и у catplot, задают height и aspect:

сетка col и row: два измерения разреза
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd

df = pd.DataFrame({
    "город": (["Москва"] * 6) + (["Казань"] * 6),
    "канал": (["онлайн", "магазин"] * 6),
    "чек": [1240, 830, 1520, 990, 1110, 700,
            620, 540, 780, 690, 610, 650],
})

g = sns.FacetGrid(df, col="город", row="канал", height=2.4, aspect=1.4)
g.map(sns.histplot, "чек", binwidth=150)
plt.show()
Вывод
График отображается под редактором

Четыре панели — четыре среза данных, собранные одним вызовом. Когда категорий много — скажем, двадцать городов — сетка по колонкам не влезет в ширину: col_wrap=4 переносит панели, как текст, по 4 в ряд. Порядок панелей совпадает с порядком значений категории в таблице, так что нужный город легко поставить первым сортировкой исходного DataFrame.

ЗадачаИнструмент
Стандартный категориальный график в сеткеcatplot с kind: bar, box, point
Распределения по группам, как в урокеFacetGrid + g.map(sns.histplot, ...)
Свой рисунок на каждой панелиFacetGrid + g.map_dataframe(функция)
Панели внутри одного графика Matplotlibplt.subplots() и ax=

Что дальше

FacetGrid завершает историю про сетки: catplot для стандартных видов, FacetGrid для всего остального. Дальше — Seaborn и Pandas DataFrame: как готовить таблицы к любому графику курса — groupby, melt, настоящие даты и сортировка.

Малые кратные — это когда каждая группа получает свою маленькую панель, и сравнение происходит взглядом слева направо.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd

df = pd.DataFrame({
    "город": ["Москва", "Казань", "Сочи"] * 3,
    "чек": [1240, 620, 350, 830, 540, 980, 1520, 780, 470],
})

g = sns.FacetGrid(df, col="город")
print(g.axes.shape)
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd

df = pd.DataFrame({
    "город": ["Москва", "Казань", "Сочи", "Пенза"],
    "чек": [1240, 620, 350, 500],
})

g = sns.FacetGrid(df, col="город", col_wrap=2)
print(g.axes.shape)
import pandas as pd

df = pd.DataFrame({
    "город": ["Москва", "Москва", "Москва", "Москва", "Москва",
              "Казань", "Казань", "Казань", "Казань", "Казань",
              "Сочи", "Сочи", "Сочи", "Сочи", "Сочи"],
    "чек": [1240, 830, 1520, 990, 1110,
            620, 540, 780, 690, 610,
            350, 980, 470, 390, 420],
})

print(df.groupby("город")["чек"].agg(["count", "mean", "max"]).round(0))
Проверь себя
0 / 5

1. В чём идея малых кратных (small multiples)?

2. Что делает g.map(sns.histplot, "чек")?

3. Чем g.map_dataframe отличается от g.map?

4. Зачем нужен col_wrap?

5. Как добавить второе измерение разреза в сетку FacetGrid?

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

Постройте FacetGrid по товарам: по панели на каждый товар (col="товар"), в каждой — гистограмма выручки с binwidth=5, общие подписи осей «Выручка, тыс. руб.» и «Число продаж». Данные в заготовке.

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

Что такое малые кратные в визуализации данных?

Small multiples — приём, при котором одна формула графика повторяется для каждой группы данных в виде маленьких одинаковых панелей. Читатель сравнивает группы взглядом слева направо без расшифровки наложенных серий. Приём популяризовал Эдвард Тюфте, а в Seaborn за него отвечают FacetGrid, catplot, relplot и displot.

Урок 18: FacetGrid на данных чеков

Чем FacetGrid отличается от catplot?

catplot — специализированная надстройка: kind="bar" или kind="box" уже содержит готовую формулу графика, остаётся передать данные. FacetGrid — общий механизм: формулу задаёте сами через g.map() или g.map_dataframe(), поэтому он подходит для любых графиков, включая собственные функции Matplotlib. Внутри catplot использует FacetGrid.

Как поменять заголовки панелей FacetGrid?

Методом g.set_titles(): шаблон "{col_name}" вставит значение категории — g.set_titles("город: {col_name}"). Формат принимает и параметры размера шрифта, как обычный заголовок. Подписи общих осей задаются g.set_axis_labels(x_label, y_label) один раз на всю сетку.

Можно ли встроить график FacetGrid в свою фигуру с subplots?

Нет: FacetGrid — figure-level контейнер, он сам создаёт фигуру и не принимает ax=. Если панели нужно положить в собственную композицию, строите сетку через plt.subplots() и рисуете в каждую ax подмножество данных фильтрацией DataFrame. Для обычных случаев проще остаться внутри FacetGrid и управлять размером через height и aspect.

Урок 5: subplots в Matplotlib

Понравился урок? Сошлитесь на него

«Малые кратные — это когда каждая группа получает свою маленькую панель, и сравнение происходит взглядом слева направо.»

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

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

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