Python: от данных до своей сводки Урок 33 из 56
matplotlib: первый график и файл, а не окно
Тридцать второй урок курса по Python. Рисуем ряд из прошлого урока: лист и оси, линия, подписи, сетка, легенда — и сохранение в файл без единого окна на экране. Плюс три вещи, которые ломают график у всех: `Agg`, порядок «сначала рисуем, потом сохраняем» и незакрытая фигура.
Зачем это нужно
Тридцать чисел в таблице читаются за полминуты, и после этого про них ничего не понятно. Та же тридцатка линией читается за секунду: видно, что ряд растёт, что двадцать шестого января был скачок и что дальше он не повторился.
За это придётся заплатить. График — самый убедительный и самый лёгкий для подделки способ показать данные: те же числа на другой шкале говорят другое. Этим займётся следующий урок — про шкалы, ноль и подписи. Сегодня — как вообще нарисовать и сохранить.
Библиотека ставится туда же, где стоит pandas:
pip install matplotlib==3.11.1
Сразу целиком
Файл grafik.py. Тот же ряд курса, что и в тридцать первом уроке, и обе линии: настоящая и сглаженная.
"""Урок 32: первый график.
Тот же ряд курса, что и в прошлом уроке. Рисуем линию, подписываем оси,
сохраняем в файл — и ни одного окна на экране.
"""
import matplotlib
# Без экрана: программа по расписанию рисует в файл, а не в окно. Эту строку
# ставят до импорта pyplot — иначе он успеет выбрать другой способ рисовать.
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import pandas as pd
from pathlib import Path
HERE = Path(__file__).resolve().parent
days, rates = [], []
start = pd.Timestamp("2026-01-05")
for i in range(40):
day = start + pd.Timedelta(days=i)
if day.weekday() >= 5:
continue
value = 512.0 + i * 0.35 + ((i * 7) % 11 - 5) * 0.6
if i == 21:
value += 14.0
days.append(day)
rates.append(value)
rate = pd.Series(rates, index=pd.DatetimeIndex(days, name="day"), name="rate").round(2)
# Figure — лист бумаги, Axes — сами оси на нём. Всё рисование идёт через ax.
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(rate.index, rate.values, color="#b03a2e", linewidth=1.6, label="курс")
ax.plot(rate.index, rate.rolling(5).mean(), color="#7f8c8d", linewidth=1.2,
linestyle="--", label="скользящее среднее, 5 дней")
ax.set_title("Курс доллара, январь — февраль 2026")
ax.set_xlabel("день")
ax.set_ylabel("тенге за доллар")
ax.grid(True, linewidth=0.4, alpha=0.5)
ax.legend()
fig.autofmt_xdate()
out = HERE / "kurs.png"
fig.savefig(out, dpi=150, bbox_inches="tight")
plt.close(fig)
print("точек на графике:", len(rate))
print("линий на осях:", len(ax.get_lines()))
print("подпись оси Y:", ax.get_ylabel())
print("файл:", out.name, "| создан:", out.exists(), "| не пустой:", out.stat().st_size > 0)
Выводит:
точек на графике: 30
линий на осях: 2
подпись оси Y: тенге за доллар
файл: kurs.png | создан: True | не пустой: True
А рядом с программой появляется kurs.png — та самая картинка.
Разбор
Agg: программа без экрана
matplotlib.use("Agg") — первая строка не случайно. По умолчанию matplotlib ищет способ показать окно, а у программы, запущенной по расписанию в четыре утра (двадцать третий урок), экрана нет. Agg — это «рисуй в файл», и с ним окно не нужно вовсе.
Ставить её надо до import matplotlib.pyplot: pyplot выбирает способ рисования при импорте, и после него менять уже поздно.
Поэтому в программе непривычный порядок импортов: сначала matplotlib, потом строка use, и только потом pyplot. Это единственное место в курсе, где импорты идут не подряд, и линтер на него ругается — а работает только так.
Лист и оси
fig, ax = plt.subplots(figsize=(8, 4)) создаёт две вещи:
fig— лист бумаги: у него есть размер в дюймах и на него сохраняют;ax— оси на этом листе: на них рисуют, их подписывают, у них своя сетка.
Рисовать можно и короче — plt.plot(...), — но тогда вы рисуете на «текущих» осях, которые библиотека держит где-то у себя. Пока график один, разницы нет; как только их два, «текущие» оси оказываются не теми. Через ax такой ошибки не бывает, и в уроках курса используется только он.
figsize задаётся в дюймах, а не в пикселях: пиксели появятся при сохранении, когда к дюймам добавится dpi.
Линия и её вид
ax.plot(x, y) рисует линию. Что стоит задавать явно:
color— цветом отличают ряды; пусть он будет один и тот же для одного и того же ряда во всех ваших графиках;linewidth— толщина; главное делают толще;linestyle="--"— пунктир для вспомогательного: сглаженная линия в примере пунктирная не для красоты, а чтобы её не путали с данными;label— подпись для легенды; без неёax.legend()нечего показывать.
Подписи — не украшение
График без подписей осей — это картинка, а не данные. Четыре строки, которые пишут всегда:
ax.set_title(...) что это вообще
ax.set_xlabel(...) что по горизонтали
ax.set_ylabel(...) что по вертикали, и в каких единицах
ax.legend() где какой ряд
ax.grid(True, linewidth=0.4, alpha=0.5) добавляет сетку — бледную, чтобы она помогала считывать значения, а не спорила с линиями. fig.autofmt_xdate() поворачивает даты, когда они не помещаются.
savefig: формат по расширению
fig.savefig("kurs.png", dpi=150, bbox_inches="tight"):
- формат берётся из расширения:
.png— картинка для письма и мессенджера,.svg— для страницы (масштабируется без потери),.pdf— для печати; dpiпревращает дюймы в пиксели: 8 дюймов при 150 — это 1200 точек по ширине. Для экрана хватает 100–150, для печати берут 300;bbox_inches="tight"обрезает пустые поля, иначе вокруг картинки остаётся воздух.
Главное правило — порядок: savefig фотографирует то, что нарисовано на момент вызова. Сохранили до plot — получили пустой лист, и никакой ошибки при этом не будет. Это третья разминка.
close: фигуры не исчезают сами
plt.close(fig) освобождает лист. Пока график один, это незаметно; программа, которая рисует по картинке в день и не закрывает их, на двадцать первый день получит предупреждение, а потом съест память. Привычка простая: нарисовали — сохранили — закрыли.
pandas умеет сам
У Series и DataFrame есть свой .plot(), и он рисует тем же matplotlib:
ax = rate.plot(figsize=(8, 4), color="#b03a2e")
ax.set_ylabel("тенге за доллар")
Он возвращает те же Axes, так что подписи и сохранение делаются как обычно: ax.figure.savefig(...). Это короче для быстрого взгляда на данные. Для отчёта, который кто-то увидит, обычно берут явный fig, ax — в нём видно всё, что происходит.
Карта урока
Скажите своими словами
Не подглядывая, ответьте вслух или на бумаге. Ответы — в конце урока.
- Зачем нужна строка
matplotlib.use("Agg")и почему она стоит до импорта pyplot? - Чем
figотличается отaxи почему в курсе рисуют черезax? - Что получится, если вызвать
savefigдоplot, и будет ли при этом ошибка?
Разминка
Три коротких шага перед заданием: предсказать, дописать, починить. Ответы — в конце урока, но сначала ответьте сами.
1. Предскажите. Что напечатает эта программа?
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(6, 3))
ax.plot([1, 2, 3], [10, 12, 11], label="ряд")
ax.set_title("проба")
ax.set_ylabel("тенге")
print("линий:", len(ax.get_lines()), "| заголовок:", ax.get_title(), "| ось Y:", ax.get_ylabel())
print("размер листа:", fig.get_size_inches().tolist(), "дюйма")
plt.close(fig)
2. Заполните пропуск. Вместо ... поставьте способ рисования, при котором программе не нужен экран.
# без этой строки программа по расписанию будет искать окно
import matplotlib
matplotlib.use(...)
import matplotlib.pyplot as plt
print("способ рисования:", matplotlib.get_backend())
fig, ax = plt.subplots()
ax.plot([1, 2], [3, 4])
plt.close(fig)
3. Почините. Файл создаётся, но картинка в нём пустая. Программа печатает, сколько линий было на осях в момент сохранения.
# сохранили раньше, чем нарисовали
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from pathlib import Path
HERE = Path(__file__).resolve().parent
fig, ax = plt.subplots()
out = HERE / "proba.png"
fig.savefig(out)
print("линий в момент сохранения:", len(ax.get_lines()))
ax.plot([1, 2, 3], [10, 12, 11])
print("линий в конце программы:", len(ax.get_lines()))
plt.close(fig)
Задание
Обязательное. Возьмите тот же ряд и нарисуйте его вместе со сглаженным. Отметьте точкой и подписью день скачка — тот самый, который сглаженная линия не показывает. Подпишите оси и заголовок, поставьте легенду и сетку. Сохраните два файла: kurs.png для письма и kurs.svg для страницы. Напечатайте: число точек, число линий на осях, заголовок, отмеченный день с его значением и по строке про каждый файл.
Ожидаемый вывод:
точек: 30 | линий на осях: 3
заголовок: Курс доллара: настоящий ряд и сглаженный
отмечен день: 2026-01-26 | значение: 532.75
kurs.png: создан True, не пустой True
kurs.svg: создан True, не пустой True
Готово, когда: вывод совпадает построчно; use("Agg") стоит до импорта pyplot; рисование идёт через ax, а не через plt; сохранение — после всего рисования; фигура закрыта; в обоих файлах картинка непустая (откройте и посмотрите).
На своих данных. Нарисуйте любой свой ряд — расходы по дням, шаги, вес. Обязательно подпишите ось Y с единицами. Потом покажите картинку человеку, который ваших данных не видел, и спросите, что он понял. Всё, чего он не понял, — это то, чего не хватает на графике.
По желанию.
- Сохраните один и тот же график в
png,svgиpdfи сравните размеры файлов. - Попробуйте
dpi=72иdpi=300и посмотрите на текст на картинке. - Нарисуйте тот же ряд через
rate.plot(ax=ax)и убедитесь, что подписи ставятся так же.
Куда это встанет в проекте
Тринадцатый шаг: у отчёта появляется картинка. sholu/suret.py рисует по линии на страну и кладёт файл рядом с CSV, а названия для легенды берёт из того же справочника, что и отчёт, — поэтому на картинке «Қазақстан», а не «KAZ».
Две строки в этом файле стоят там именно потому, что сводка работает по расписанию, а не перед человеком: matplotlib.use("Agg") до импорта pyplot — в четыре утра экрана нет; и plt.close(fig) после сохранения — иначе программа, рисующая по картинке в день, узнает про незакрытые фигуры на двадцать первый день.
Долги. Цвета заданы прямо в коде списком. Пока рядов три, это нормально; когда их станет больше, цвета захотят жить в одном месте вместе с остальным видом отчёта.
Ответы
Показать ответы
На вопросы
- Она говорит matplotlib рисовать в файл, а не в окно. Программе, запущенной по расписанию, экран недоступен, и без этой строки она попытается его найти. Ставится до импорта
pyplot, потому что pyplot выбирает способ рисования при импорте — после него менять поздно. fig— лист: размер, поля, сохранение.ax— оси на листе: линии, подписи, сетка, легенда. Черезaxрисуют потому, чтоplt.plotрисует на «текущих» осях, а когда графиков становится два, текущие оказываются не теми.- Получится пустая картинка, и ошибки не будет.
savefigсохраняет то, что нарисовано в момент вызова; всё, что нарисовали после, в файл не попадёт.
К разминке
axзнает, что на нём нарисовано и как подписано, аfig— свой размер в дюймах, тот самый, что задан вfigsize.
линий: 1 | заголовок: проба | ось Y: тенге
размер листа: [6.0, 3.0] дюйма
matplotlib.use("Agg"). Имя способа рисования пишется строкой, иget_backend()потом возвращает его же.
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
print("способ рисования:", matplotlib.get_backend())
fig, ax = plt.subplots()
ax.plot([1, 2], [3, 4])
plt.close(fig)
способ рисования: Agg
- Перенести
savefigпослеplot. Ноль линий в момент сохранения — это и есть ответ: в файле пусто, хотя программа отработала без единой жалобы.
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from pathlib import Path
HERE = Path(__file__).resolve().parent
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [10, 12, 11])
out = HERE / "proba.png"
fig.savefig(out)
print("линий в момент сохранения:", len(ax.get_lines()))
print("линий в конце программы:", len(ax.get_lines()))
plt.close(fig)
линий в момент сохранения: 1
линий в конце программы: 1
К заданию
Точка и подпись ставятся на настоящий ряд: ax.plot([день], [значение], marker="o") и ax.annotate(...). Смысл задания в том, чтобы на картинке было видно ровно то, о чём говорил прошлый урок, — сглаженная линия проходит мимо события, и рядом с ней это заметно с одного взгляда.
Два файла сохраняются из одной фигуры: savefig можно звать сколько угодно раз, фигура от этого не меняется. Закрывают её один раз, после всех сохранений.
Источники
- matplotlib: краткое введение —
Figure,Axesи почему их два. - Выбор способа рисования — что такое
Aggи когда он нужен. - Сохранение картинок — форматы,
dpiи поля.
Если вы нашли ошибку или опечатку в тексте статьи, то сообщите нам об этом
Комментарии (0)
Войдите, чтобы оставить комментарий →
Пока нет комментариев. Будьте первым.