Семестр 3 · Модуль 3 · Неделя 7

Matplotlib и Seaborn: честные графики

Объектная модель Figure/Axes · виды графиков · subplots · подписи · сохранение · Seaborn: heatmap/pairplot · честная визуализация

2 ч теории. Презентация — 30 мин мини-лекция, далее live-coding и задачи семинара. Данные — titanic.csv (модуль 2).

Прежде чем начать: откройте Jupyter и загрузите titanic.csv через pd.read_csv — сегодня мы впервые увидим свои данные.

График — язык отчётов и научных статей

Какие данные вы считали в модуле 2? Как бы вы показали «средний возраст по классу каюты» человеку, который не читает таблицы?

Два API Matplotlib: pyplot и объектный

import matplotlib.pyplot as plt plt.plot(x, y) # «быстрый» стиль plt.title('...') plt.show()
fig, ax = plt.subplots() # объектный ax.plot(x, y) ax.set_title('...') fig.savefig('plot.png')
Почему plt.xlabel(...) после fig, ax = plt.subplots() — смешение стилей и источник ошибок?

Анатомия рисунка: Figure и Axes

fig, axes = plt.subplots(2, 2, figsize=(9.5, 7)) # axes — массив 2×2; figsize — размер холста в дюймах axes[0, 0].set_title('левый верхний') axes[1, 1].set_title('правый нижний')

Одна Figure — много Axes. Каждый Axes можно настраивать независимо: title, xlabel, ylabel, legend, grid.

Сколько «графиков» в plt.subplots(2, 3) и какая форма у axes?

plot и scatter: тренд и связь двух величин

fig, ax = plt.subplots() ax.scatter(df['Age'], df['Fare'], s=40, alpha=0.75, color='#2563eb') ax.set_xlabel('возраст, лет') ax.set_ylabel('цена билета, у.е.') ax.set_title('Возраст и цена билета') ax.grid(alpha=0.3)
данные: titanic.csv, 40 пассажиров

plot — для временны́х рядов и трендов; scatter — для связи двух количественных величин.

Какая закономерность видна на графике: возраст и цена связаны? Почему нельзя утверждать «цена растёт с возрастом»?

bar: сравнение категорий — с осью Y от нуля

rate = df.groupby('Pclass')['Survived'].mean() fig, ax = plt.subplots() ax.bar([1, 2, 3], rate, color='#059669') ax.set_ylim(0, 1) # честная ось с нуля ax.set_xticks([1, 2, 3]) ax.set_xlabel('класс каюты') ax.set_ylabel('доля выживших') ax.set_title('Выживаемость по классу')
доля выживших = mean(Survived) по классам

Правило: у bar-диаграммы ось Y начинается с нуля. Обрезанная ось set_ylim(0.2, 0.5) «раздувает» разницу между столбцами.

Почему set_ylim(0.3, 0.5) на этом графике — манипуляция? Что она скрывает?

hist и boxplot: распределение одной величины

fig, ax = plt.subplots() ax.hist(df['Age'].dropna(), bins=12, color='#d97706', edgecolor='white') ax.set_xlabel('возраст, лет') ax.set_ylabel('число пассажиров') ax.set_title('Распределение возраста')
38 пассажиров с известным возрастом
Сколько пассажиров младше ~20 лет? Как это понять из гистограммы?

Subplots 2×2: сетка осей, часть 1

fig, axes = plt.subplots(2, 2, figsize=(9.5, 7)) axes[0,0].hist(df['Age'].dropna(), bins=10, color='#d97706', edgecolor='white') axes[0,0].set_title('Распределение возраста') axes[0,0].set_xlabel('возраст, лет'); axes[0,0].set_ylabel('число') rate = df.groupby('Pclass')['Survived'].mean() axes[0,1].bar([1, 2, 3], rate, color='#059669') axes[0,1].set_ylim(0, 1); axes[0,1].set_xticks([1, 2, 3]) axes[0,1].set_title('Выживаемость по классу') axes[0,1].set_xlabel('класс'); axes[0,1].set_ylabel('доля выживших')

Сетка создаётся одним вызовом; каждая ось настраивается независимо по индексу axes[i, j]. Продолжение — следующий слайд.

Какой график вы разместите в axes[1, 0], если нужно показать связь возраста и цены билета?

…и заполняем оставшиеся оси (продолжение)

axes[1,0].scatter(df['Age'], df['Fare'], s=30, alpha=0.7, color='#2563eb') axes[1,0].set_title('Возраст × цена билета') axes[1,0].set_xlabel('возраст, лет'); axes[1,0].set_ylabel('цена, у.е.') sns.boxplot(data=df, x='Sex', y='Age', ax=axes[1,1]) axes[1,1].set_title('Возраст по полу') axes[1,1].set_xlabel('пол'); axes[1,1].set_ylabel('возраст, лет') fig.tight_layout() fig.savefig('figures/dashboard.png', dpi=110)
дашборд 2×2, файл dashboard.png

Итог — дашборд из четырёх подписанных графиков. Это артефакт недели: так же вы соберёте EDA-ноутбук в проекте модуля.

Зачем нужен fig.tight_layout() перед сохранением?

Подписи, легенда, сетка — обязательная часть

ax.legend() ax.grid(alpha=0.3) ax.set_xticks([1, 2, 3])
Чем ax.legend() без label= отличается от легенды с заданными метками? Что увидит студент?

Сохранение: savefig — только до show

fig, ax = plt.subplots() ax.plot(df.index, df['Fare'], color='#7c3aed') ax.set_title('Цена билета по списку') ax.set_xlabel('пассажир (номер)'); ax.set_ylabel('цена, у.е.') fig.savefig('figures/fare.png', dpi=150, bbox_inches='tight') # ДО show!
fig.savefig('fare.png') # после plt.show() # → пустой/белый файл: figure уже закрыт
Как проверить, что файл сохранился, не открывая его глазами?

Честная визуализация: масштаб не должен врать

слева — обрезанная ось (манипуляция); справа — ось с нуля
  • Оба графика построены на одних данных (число выживших по классам).
  • Слева set_ylim(4, 9) — столбцы «почти равны», но разрыв визуально утроен.
  • Справа ось с нуля — честная картина: 1-й класс ≈ 5, 3-й ≈ 8.

Правила честности: бары с нуля; не обрезать ось без маркера разрыва; не скрывать выбросы; подписывать единицы. Полный разбор — неделя 8.

Какую историю рассказывает обрезанный график, а какую — честный? Кто из читателей поверит первому?

Seaborn: heatmap корреляций

import seaborn as sns fig, ax = plt.subplots(figsize=(6.2, 4.6)) sns.heatmap(df[['Age', 'Fare', 'Pclass', 'Survived']].corr(), annot=True, fmt='.2f', cmap='coolwarm', ax=ax) ax.set_title('Корреляции признаков Titanic')
df.corr() считает только числовые колонки
Почему в df.corr() не попали колонки Name и Sex? Что с ними сделать, чтобы учесть?

Seaborn: boxplot и pairplot из DataFrame

sns.boxplot(data=df, x='Sex', y='Age') sns.pairplot(df[['Age', 'Fare', 'Pclass', 'Survived']].dropna(), hue='Survived', diag_kind='hist')
pairplot: попарные scatter + распределения по выживаемости

Seaborn — «статистическая надстройка» над Matplotlib: принимает DataFrame напрямую, добавляет стиль по умолчанию и сразу рисует группировку по hue.

Какой вопрос к данным можно быстро ответить, глядя на pairplot с hue='Survived'?

Типичные ошибки недели 7

  1. Неподписанные графики — нет title/xlabel/ylabel. График без подписей не считается сданным (чек-лист ревью).
  2. savefig после show — пустой/белый файл.
  3. Смешение стилей: plt.xlabel после создания axes.
  4. Легенда без метокlabel= не задан.
  5. Обрезанная ось Y у bar — «раздутая» разница между категориями.
  6. df.corr().plot() и sns.heatmap(df.corr()) без понимания, что корреляция — по числовым колонкам.
  7. График строится в цикле, но все линии рисуются в одни оси — нужны новые subplot или цвет/метка каждой серии.
Студент показал график с заголовком, но без подписей осей. Примете ли вы его как «сданный»? Что скажете?

Вопросы для проверки понимания

  1. Зачем нужен объект Axes и чем ax.plot лучше plt.plot?
  2. Как создать сетку 2×2 и подписать каждый график?
  3. Как сохранить график в PNG высокого качества (и почему именно до show)?
  4. Почему нельзя обрезать ось Y у bar-диаграммы?
  5. Какие колонки попадают в df.corr() и что показывает heatmap?

Ответьте письменно за 3 минуты — это мини-самооценка перед семинаром.

Переход: на семинаре вы соберёте дашборд 2×2, тепловую карту и научитесь «чинить» манипулирующие графики.

Что дальше

Готовы? Откройте семинар-7.md, скопируйте titanic.csv в data/ и выполните базовую задачу.