Настройка графика: подписи, легенда, сетка

Сырой график — это полуфабрикат. Подписи, легенда и аннотации превращают его в сообщение.

«График без подписей осей — это загадка. Зритель не обязан угадывать, что значат числа».

График из коробки рисует данные, но не объясняет их. Профессиональная визуализация всегда несёт контекст: что на осях, в каких единицах, что значат цвета, где главная точка. Matplotlib даёт полный набор методов на объекте Axes.

fig, ax = plt.subplots(figsize=(8, 5))

ax.plot(months, revenue, marker="o", label="Выручка")
ax.plot(months, costs,   marker="s", label="Затраты")

ax.set_title("Выручка и затраты за полугодие")
ax.set_xlabel("Месяц")
ax.set_ylabel("Тыс. руб.")
ax.set_ylim(0, None)            # ось Y от нуля — честно
ax.grid(True, alpha=0.3)        # лёгкая сетка
ax.legend()                    # легенда по label=...

# аннотация ключевой точки
ax.annotate("Пик продаж", xy=(months[3], revenue[3]),
            xytext=(months[3], revenue[3] + 20),
            arrowprops=dict(arrowstyle="->"))

fig.tight_layout()
fig.savefig("report.png", dpi=150)

Обратите внимание на set_ylim(0, None): ось Y начинается с нуля — это вопрос честности, к которому мы вернёмся в разделе про искажения. tight_layout() убирает обрезание подписей.

Почему на оформление вообще стоит тратить силы? Потому что график почти всегда живёт дольше и дальше, чем код, который его породил: его вставят в слайд, отправят в чат, распечатают в отчёт — без вас рядом, чтобы объяснить. График, оторванный от контекста, должен сам отвечать на три вопроса: что по осям, в каких единицах и какой здесь главный вывод. Поэтому заголовок лучше делать не описательным («Выручка по месяцам»), а выводным («Выручка выросла на 58% за полугодие»): тогда зритель уносит мысль, а не просто факт наличия графика. Хороший заголовок — это короткое предложение, а не ярлык.

Полезно знать чуть больше методов настройки. ax.set_xticks() и ax.set_xticklabels() управляют делениями вручную — например, повернуть длинные подписи: ax.tick_params(axis="x", rotation=45). ax.axhline(y=0) и ax.axvline() рисуют опорные линии (ноль, целевое значение, среднее). ax.text() ставит произвольную надпись в координатах данных, а transform=ax.transAxes переключает её в относительные координаты 0..1, удобные для пометок в углу. Форматирование чисел на оси задаёт ax.yaxis.set_major_formatter() — так превращают «1000000» в «1 млн» или добавляют знак процента. Эти мелочи и отличают черновик от графика, который не стыдно показать.

Как работает под капотом

Каждый set_* меняет свойство соответствующего artist (заголовка, оси, сетки). Легенда строится из пар «artist + label»: вы задаёте label= при рисовании, а legend() собирает их. Аннотация — это текстовый artist плюс стрелка-artist с координатами в системе данных. savefig запускает backend (Agg для PNG), который растеризует дерево объектов.

Аннотации часто ставят на экстремумы. Найдём их вручную — это и есть «куда поставить стрелку».

# Находим пик и провал в ряду — кандидаты на аннотацию
months = ["янв", "фев", "мар", "апр", "май", "июн"]
revenue = [120, 135, 150, 190, 175, 160]

peak_i = revenue.index(max(revenue))
low_i = revenue.index(min(revenue))

print("Пик:", months[peak_i], max(revenue))
print("Провал:", months[low_i], min(revenue))

# рост месяц к месяцу
for i in range(1, len(revenue)):
    delta = revenue[i] - revenue[i - 1]
    sign = "+" if delta >= 0 else ""
    print(months[i], sign + str(delta))

«Попробуй сам ▶» — экстремумы и дельты, которые вы посчитали, обычно и подписывают на графике стрелками.

Частые ошибки

График без подписей осей и единиц. Легенда, перекрывающая данные (используйте loc= или bbox_to_anchor). Слишком плотная сетка, забивающая данные. Подписи обрезаны — забыли tight_layout(). Чрезмерные аннотации — каждая точка подписана.

Добавим типичные огрехи оформления, которые портят даже верный по сути график. Описательный заголовок вместо выводного: «График выручки» ничего не сообщает — лучше вынести в заголовок сам вывод. Нечитаемые подписи оси X, налезающие друг на друга — их нужно повернуть (rotation=45) или перейти на горизонтальный barh. Дублирование информации: и легенда, и прямые подписи у линий одновременно — выберите что-то одно, прямые подписи у концов линий часто читаются лучше легенды. Слишком мелкий шрифт для слайда или печати — то, что разборчиво на экране ноутбука, исчезает на проекторе; задавайте размер шрифта осознанно. Контейнерные «рамки» (верхняя и правая линии, spines) добавляют визуального шума — их часто убирают через ax.spines["top"].set_visible(False). И сохранение в низком DPI: график для отчёта в 72 dpi выглядит мыльным, для печати нужно 150–300.

Best practices

  • Обязательны: заголовок-вывод, подписи осей, единицы измерения.
  • Сетка — бледная (alpha=0.3), чтобы не спорить с данными.
  • Аннотируйте только ключевые точки — 1–2, не все.
  • Сохраняйте в высоком DPI для отчётов (dpi=150 и выше).

Итог: вы умеете делать график читаемым. В следующем разделе пройдём по конкретным типам графиков и научимся выбирать их осознанно.

Проверьте себя
1. Зачем вызывать fig.tight_layout()?
AЧтобы ускорить рендеринг
BЧтобы подписи и заголовки не обрезались полями фигуры
CЧтобы добавить сетку
DЧтобы сохранить файл
2. Как Matplotlib собирает легенду?
AИз имён переменных
BИз значений label=, заданных при рисовании, по вызову legend()
CИз заголовка графика
DСлучайным образом