Коротко: График выбирают под аналитическую задачу, а не под красоту. Сравниваешь категории — bar (столбики). Показываешь динамику во времени — line (линия). Смотришь распределение — hist, box или violin. Ищешь связь двух переменных — scatter или heatmap. Доли — pie только для 2-3 сегментов, чаще честнее тот же bar. matplotlib даёт полный контроль, seaborn — быстрые статистические графики из коробки. Ниже — карта выбора, код на обеих библиотеках рядом и разбор ошибок оформления.
Как выбрать график под конкретную аналитическую задачу?
Сначала формулируешь вопрос к данным, потом подбираешь тип графика. Одна задача — один тип. Если внутри одного изображения ты пытаешься ответить сразу на три вопроса, зритель не поймёт ни одного.
Вот рабочая карта соответствий:
| Задача | Вопрос к данным | Тип графика | matplotlib | seaborn |
|---|---|---|---|---|
| Сравнение | Где больше, где меньше? | Столбики | bar, barh | barplot, countplot |
| Динамика | Как менялось во времени? | Линия | plot | lineplot |
| Распределение | Как разбросаны значения? | Гистограмма/ящик/скрипка | hist, boxplot | histplot, boxplot, violinplot |
| Связь | Как связаны две переменные? | Точки/тепловая карта | scatter, imshow | scatterplot, heatmap |
| Доли | Из чего состоит целое? | Круг/стек | pie, bar (stacked) | нет своего pie |
| Состав + сравнение | Доли по нескольким группам | Стек-бар | bar(bottom=...) | histplot(multiple='stack') |
Дальше разберём каждую задачу с кодом. Подготовим один датасет, чтобы примеры были сквозными.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
rng = np.random.default_rng(42)
df = pd.DataFrame({
'city': rng.choice(['Москва', 'СПб', 'Казань', 'Новосибирск'], 500),
'channel': rng.choice(['organic', 'ads', 'referral'], 500),
'revenue': rng.gamma(shape=2.0, scale=1500, size=500).round(0),
'sessions': rng.poisson(8, 500) + 1,
})
df['month'] = pd.to_datetime('2026-01-01') + pd.to_timedelta(rng.integers(0, 180, 500), unit='D')
Какой график взять для сравнения категорий?
Для сравнения значений по категориям бери столбчатую диаграмму (bar). Глаз человека точно сравнивает длину столбиков от общей базовой линии — это самый читаемый способ ответить на вопрос «где больше».
Правила, которые отличают хороший bar-chart от плохого:
- Сортируй столбцы по значению, а не по алфавиту (кроме случаев, когда порядок категорий сам по себе осмыслен — например, дни недели).
- Если названий категорий много или они длинные — делай горизонтальные столбики (
barh), подписи читаются без наклона. - Ось Y обязательно начинается с нуля. Обрезанная ось врёт про соотношение длин.
matplotlib и seaborn рядом:
agg = df.groupby('city', as_index=False)['revenue'].sum().sort_values('revenue')
# matplotlib
fig, ax = plt.subplots(figsize=(7, 4))
ax.barh(agg['city'], agg['revenue'], color='#4C72B0')
ax.set_xlabel('Выручка, ₽')
ax.set_title('Выручка по городам')
# seaborn — агрегация внутри
sns.barplot(data=df, x='revenue', y='city', estimator='sum',
order=agg['city'], errorbar=None, color='#4C72B0')
Ключевая разница уже видна: seaborn сам агрегирует данные (estimator='sum') и по умолчанию рисует доверительный интервал, который здесь мы отключили через errorbar=None. matplotlib ждёт готовые числа. Если нужен просто подсчёт строк по категории — в seaborn есть countplot, аналог value_counts().plot.bar().
Сравнение категорий — базовая операция в аналитике. Те же группировки ты делаешь в SQL через GROUP BY; если хочешь закрепить агрегаты, посмотри гайд по GROUP BY и HAVING и потренируй запросы в SQL-тренажёре.
Как показать динамику во времени?
Для временных рядов бери линейный график (line). Линия подчёркивает непрерывность и тренд: зритель считывает направление и наклон, а не отдельные точки.
Когда что использовать:
- Одна метрика во времени — простая линия.
- Несколько сегментов — несколько линий на одних осях (не больше 4-5, иначе «спагетти»).
- Разреженные, несвязанные наблюдения — тогда честнее точки, а не линия, соединяющая то, что физически не связано.
ts = (df.set_index('month')
.groupby([pd.Grouper(freq='ME'), 'channel'])['revenue']
.sum().reset_index())
# matplotlib — линия на канал вручную
fig, ax = plt.subplots(figsize=(8, 4))
for ch, part in ts.groupby('channel'):
ax.plot(part['month'], part['revenue'], marker='o', label=ch)
ax.legend(title='Канал')
ax.set_ylabel('Выручка, ₽')
# seaborn — hue разводит линии сам
sns.lineplot(data=ts, x='month', y='revenue', hue='channel', marker='o')
Здесь виден главный плюс seaborn: параметр hue автоматически разбивает данные по категории и раскрашивает линии, добавляя легенду. В matplotlib этот цикл пишешь руками. Зато matplotlib даёт полный контроль над каждой линией — толщиной, стилем, аннотациями к конкретным точкам.
Помесячную агрегацию удобно готовить в pandas через resample — это разобрано в части про временные ряды. Динамику метрик вроде DAU или retention D7 почти всегда показывают именно линией.
Как визуализировать распределение (hist, box, violin)?
Чтобы понять, как разбросаны значения одной числовой переменной, есть три инструмента: гистограмма, ящик с усами и скрипка. Они отвечают на разные подвопросы.
- Гистограмма (hist) — форма распределения: где пик, есть ли второй горб, скошено вправо или симметрично.
- Ящик с усами (box) — компактная сводка: медиана, квартили, выбросы. Идеален, когда нужно сравнить распределение по многим группам.
- Скрипка (violin) — box плюс сглаженная плотность: видно и форму, и статистики. Хорош, когда распределение бимодальное и box это скрывает.
# Гистограмма
fig, ax = plt.subplots(figsize=(7, 4))
ax.hist(df['revenue'], bins=30, color='#55A868', edgecolor='white')
ax.set_xlabel('Выручка на заказ, ₽')
# seaborn — histplot с оценкой плотности
sns.histplot(df['revenue'], bins=30, kde=True)
# box и violin по группам — здесь seaborn заметно короче
sns.boxplot(data=df, x='channel', y='revenue')
sns.violinplot(data=df, x='channel', y='revenue')
Именно на распределениях seaborn экономит больше всего кода. Нарисовать box по каждому каналу в чистом matplotlib — это ручная группировка и передача списка массивов в boxplot. В seaborn достаточно указать x и y.
Важный аналитический момент: число бинов гистограммы (bins) меняет вывод. Слишком мало — прячешь структуру, слишком много — ловишь шум. Начинай с 20-30 и смотри. И помни, что средняя по скошенному распределению обманчива — почему медиана честнее среднего, разобрано в материале про медиану и перцентили.
Как показать связь двух переменных (scatter, heatmap)?
Связь двух числовых переменных показывает диаграмма рассеяния (scatter), а связь многих переменных сразу — тепловая карта корреляций (heatmap).
Scatter отвечает на вопрос «когда растёт X, что делает Y»: видно направление, силу и форму зависимости, а также выбросы, которые выпадают из облака точек.
# matplotlib scatter, цвет и размер кодируют доп. переменные
fig, ax = plt.subplots(figsize=(7, 5))
sc = ax.scatter(df['sessions'], df['revenue'],
c=df['sessions'], s=25, alpha=0.6, cmap='viridis')
ax.set_xlabel('Сессий'); ax.set_ylabel('Выручка, ₽')
fig.colorbar(sc, label='Сессий')
# seaborn — hue/size одной строкой + линия тренда через regplot
sns.scatterplot(data=df, x='sessions', y='revenue', hue='channel', alpha=0.6)
sns.regplot(data=df, x='sessions', y='revenue', scatter_kws={'alpha': 0.3})
Когда точек тысячи и они слипаются в чёрное пятно — снижай alpha, бери s поменьше или переходи на hexbin (matplotlib) / histplot с двумя осями. Это называется проблемой overplotting.
Тепловая карта нужна, когда переменных много и хочется увидеть все парные корреляции разом:
corr = df[['revenue', 'sessions']].join(
pd.get_dummies(df['channel'])).corr(numeric_only=True)
sns.heatmap(corr, annot=True, fmt='.2f', cmap='coolwarm',
center=0, vmin=-1, vmax=1)
Два правила для honest heatmap: используй расходящуюся палитру (coolwarm, RdBu) с центром в нуле, чтобы положительная и отрицательная корреляция читались по-разному, и всегда фиксируй vmin/vmax, иначе цвета «поплывут» между графиками. Корреляция — это не причинность; прежде чем делать выводы, вспомни про парадокс Симпсона.
Стоит ли вообще рисовать круговую диаграмму (pie)?
Честный ответ: pie оправдан только для 2-3 сегментов, которые в сумме дают 100% и заметно различаются. Во всех остальных случаях столбчатая диаграмма читается лучше, потому что глаз плохо сравнивает углы и площади секторов.
Проблемы круговых диаграмм:
- Пять и больше секторов близкого размера различить невозможно.
- Нельзя показать динамику — pie статичен по определению.
- 3D-пироги искажают доли перспективой: ближний сектор кажется больше. Никогда их не делай.
Если очень нужны доли, сравни два подхода:
shares = df.groupby('channel')['revenue'].sum()
# pie — допустимо только потому что каналов всего 3
fig, ax = plt.subplots(figsize=(5, 5))
ax.pie(shares, labels=shares.index, autopct='%1.1f%%', startangle=90)
ax.axis('equal')
# честная альтернатива — горизонтальный bar долей
pct = (shares / shares.sum() * 100).sort_values()
fig, ax = plt.subplots(figsize=(7, 3))
ax.barh(pct.index, pct.values, color='#C44E52')
ax.set_xlabel('Доля выручки, %')
У seaborn своего pie нет — и это осознанная позиция авторов библиотеки. Когда сегментов много или нужна их динамика во времени, вместо пирога бери stacked bar или несколько линий. Доли часто считают для сегментаций вроде RFM — там результат тоже нагляднее в виде столбиков по сегментам.
matplotlib или seaborn — в чём разница и что выбрать?
seaborn построен поверх matplotlib. Это не конкуренты, а два слоя одного стека: seaborn — высокоуровневый интерфейс для статистических графиков, matplotlib — низкоуровневый движок, который всё в итоге рисует.
| Критерий | matplotlib | seaborn |
|---|---|---|
| Уровень | Низкий, полный контроль | Высокий, короткий код |
| Агрегация данных | Делаешь сам заранее | Встроена (estimator, hue, ci) |
| Работа с DataFrame | Передаёшь массивы | Понимает колонки по имени |
| Статистика из коробки | Нет | box/violin/kde/regplot/heatmap |
| Тонкая доводка | Полная | Через доступ к осям matplotlib |
| Оформление по умолчанию | Простое | Приятные темы и палитры |
Практический вывод: seaborn для быстрого разведочного анализа (EDA), когда за 5 минут нужно посмотреть 20 срезов. matplotlib — когда доводишь один график до презентации или дашборда и нужен контроль над каждым пикселем. Хорошая новость: их не надо выбирать раз и навсегда. Строишь основу в seaborn, потом берёшь объект ax и дорабатываешь инструментами matplotlib:
ax = sns.barplot(data=df, x='channel', y='revenue', estimator='sum', errorbar=None)
ax.set_title('Выручка по каналам', fontsize=14, weight='bold')
ax.bar_label(ax.containers[0], fmt='%.0f') # это уже matplotlib
ax.spines[['top', 'right']].set_visible(False)
plt.tight_layout()
Обе библиотеки удобно гонять в Python-тренажёре прямо в браузере — код исполняется через Pyodide, ничего ставить локально не нужно. Если базового pandas ещё не хватает, начни с курса по pandas, а затем возвращайся к визуализации.
Какие ошибки оформления убивают график?
Даже правильный тип графика можно испортить оформлением. Вот чек-лист ошибок, которые чаще всего встречаются в дашбордах и на собеседованиях.
- Обрезанная ось Y на bar-chart. Начинаешь ось не с нуля — и разница в 3% выглядит как разница в 3 раза. На line-графике обрезать ось иногда допустимо (тренд важнее абсолюта), на bar — почти никогда.
- Перегруз. Десять линий, три легенды, сетка поверх данных. Каждый элемент, который не несёт информации, — это шум. Убирай верхнюю и правую рамку, лишние линии сетки, дубли подписей.
- 3D. Любой 3D-эффект искажает восприятие длин и площадей. Плоский график всегда честнее.
- Радуга вместо палитры. Для непрерывной величины бери последовательную палитру (
viridis), для отклонений от центра — расходящуюся (coolwarm). Jet/rainbow вводит в заблуждение: одинаковые шаги цвета соответствуют неравным шагам значения. - Нет подписей осей и единиц. График без «₽», «%», «дней» заставляет зрителя гадать.
- Категории не отсортированы. Алфавитный порядок вместо порядка по значению прячет главный вывод.
- Слишком много бинов или, наоборот, слишком мало на гистограмме.
Минимальная «уборка» графика перед показом:
fig, ax = plt.subplots(figsize=(7, 4))
ax.bar(agg['city'], agg['revenue'], color='#4C72B0')
ax.set_ylabel('Выручка, ₽')
ax.set_title('Выручка по городам, 2026')
ax.spines[['top', 'right']].set_visible(False) # убрать лишние рамки
ax.grid(axis='y', alpha=0.3) # мягкая сетка только по Y
ax.margins(x=0.02)
plt.tight_layout()
Умение выбрать и оформить график регулярно проверяют на интервью — от «когда bar, а когда line» до «что не так с этой диаграммой». Прогони типовые вопросы в разделе интервью или потренируй защиту решений на AI-собеседовании, где можно отвечать голосом и получать разбор.
С чего начать тренировку в визуализации?
Собери короткий чек-лист под себя: вопрос к данным → тип графика → оформление. Сравнение — bar с нуля и сортировкой. Динамика — line, до пяти сегментов. Распределение — hist для формы, box/violin для сравнения групп. Связь — scatter с прозрачностью, heatmap с центром в нуле. Доли — bar, а pie только для 2-3 сегментов.
Дальше усложняй постепенно. Сначала научись за минуту выбирать правильный тип под любой вопрос — это самое частое место, где ошибаются даже опытные аналитики. Потом отработай оформление: подписи осей с единицами, сортировку категорий, убранные лишние рамки. И только затем берись за композицию из нескольких панелей и аннотации, которые ведут взгляд читателя к главному выводу. Не пытайся выучить все параметры matplotlib наизусть — держи под рукой карту выбора из этой статьи и документацию, а синтаксис осядет сам через повторение.
Лучший способ закрепить — строить графики на своих данных, а не переписывать чужие примеры. В Python-тренажёре первые 5 задач открыты бесплатно: берёшь датасет, отвечаешь на вопрос нужным графиком и сразу видишь результат. Дальше, если зайдёт, открывается полный набор по Pro — с задачами на визуализацию, pandas и разбор реальных кейсов. А чтобы понять, что вообще стоит показывать на графике, держи рядом шпаргалку по pandas и разбор того, как презентовать результаты аналитики.