Построение графиков на Python с помощью Matplotlib (Руководство)

Оглавление

Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Построение графиков на Python с помощью Matplotlib

Картинка стоит тысячи слов, а с помощью библиотеки Python matplotlib, к счастью, для создания графики производственного качества требуется гораздо меньше тысячи слов кода.

Однако matplotlib - это еще и массивная библиотека, и добиться того, чтобы график выглядел правильно, часто удается методом проб и ошибок. Использовать однострочники для создания базовых графиков в matplotlib довольно просто, но умелое управление оставшимися 98% библиотеки может оказаться непростой задачей.

Эта статья представляет собой пошаговое руководство по matplotlib от начального до среднего уровня, в котором теория сочетается с примерами. Хотя обучение на примерах может быть чрезвычайно полезным, оно также помогает получить хотя бы поверхностное представление о внутреннем устройстве библиотеки и ее планировке.

Вот о чем мы расскажем:

  • Pylab и pyplot: что есть что?
  • Ключевые концепции дизайна matplotlib
  • Понимание plt.subplots()
  • Визуализация массивов с помощью matplotlib
  • Построение графика с помощью комбинации pandas + matplotlib

Бесплатный бонус: Нажмите здесь, чтобы загрузить 5 примеров на Python + Matplotlib с полным исходным кодом , которые вы можете использовать в качестве основы для создания ваших собственных графиков.

В этой статье предполагается, что пользователь немного знаком с NumPy. В основном мы будем использовать модуль numpy.random для генерации “игрушечных” данных, извлекая выборки из различных статистических распределений.

Если у вас еще не установлен matplotlib, ознакомьтесь с здесь для получения подробного руководства, прежде чем продолжить.

Почему Matplotlib Может Сбивать С Толку?

Изучение matplotlib иногда может быть неприятным процессом. Проблема не в том, что отсутствует документация по matplotlib: документация на самом деле обширная. Но следующие проблемы могут вызвать некоторые затруднения:

  • Библиотека сама по себе огромна, всего около 70 000 строк кода.
  • Matplotlib содержит несколько различных интерфейсов (способов построения фигуры) и способен взаимодействовать с несколькими различными серверными частями. (Серверные части отвечают за процесс реального отображения диаграмм, а не только за их внутреннюю структуру.)
  • Несмотря на то, что она является всеобъемлющей, часть собственной общедоступной документации matplotlib серьезно устарела. Библиотека все еще развивается, и многие старые примеры, доступные в Интернете, в современной версии могут занимать на 70% меньше строк кода.

Итак, прежде чем мы перейдем к каким-либо ярким примерам, полезно ознакомиться с основными концепциями дизайна matplotlib.

Pylab: Что это такое и стоит ли мне его использовать?

Давайте начнем с небольшого экскурса в историю: Джон Д. Хантер, нейробиолог, начал разрабатывать matplotlib примерно в 2003 году, изначально вдохновленный идеей эмулировать команды из программного обеспечения MATLAB от Mathworks. Джон трагически скончался молодым в возрасте 44 лет в 2012 году, и matplotlib теперь является полноценным проектом сообщества, разрабатываемым и поддерживаемым множеством других разработчиков. (Джон выступил с докладом об эволюции matplotlib на конференции SciPy 2012 года, на которую стоит обратить внимание.)

Одной из важных особенностей MATLAB является его глобальный стиль. Концепция импорта на языке Python не очень широко используется в MATLAB, и большинство функций MATLAB легко доступны пользователю на верхнем уровне.

Знание того, что matplotlib берет свое начало в MATLAB, помогает объяснить, почему существует pylab. pylab - это модуль в библиотеке matplotlib, созданный для имитации глобального стиля MATLAB. Он существует только для того, чтобы перенести ряд функций и классов из NumPy и matplotlib в пространство имен , упрощая переход для бывших пользователей MATLAB, которые не привыкли к использованию инструкций import.

Бывшие новообращенные из MATLAB (которые все прекрасные люди, я обещаю!) понравилась эта функциональность, потому что с помощью from pylab import * они могли просто вызывать plot() или array() напрямую, как это было бы в MATLAB.

Для некоторых пользователей Python проблема может быть очевидна: использование from pylab import * в сеансе или скрипте, как правило, является плохой практикой. Matplotlib теперь прямо рекомендует не делать этого в своих собственных руководствах:

“[pylab] все еще существует по историческим причинам, но настоятельно рекомендуется его не использовать. Это загрязняет пространства имен функциями, которые затеняют встроенные функции Python и могут привести к трудноотслеживаемым ошибкам. Чтобы добиться интеграции с IPython без импорта, предпочтительнее использовать магию %matplotlib.” [ Источник]

Внутри системы существует множество потенциально конфликтующих импортных данных, которые маскируются в коротком pylab источнике . Фактически, использование ipython --pylab (из терминала/командной строки) или %pylab (из IPython/Jupyter tools) просто вызывает from pylab import * под капотом.

Суть в том, что matplotlib отказался от этого удобного модуля и теперь явно рекомендует не использовать pylab, что приводит ситуацию в соответствие с одним из ключевых понятий Python: явное лучше неявного.

Не прибегая к pylab, мы обычно можем обойтись только одним каноническим импортом:

>>> import matplotlib.pyplot as plt

Раз уж мы об этом заговорили, давайте также импортируем NumPy, который мы будем использовать для генерации данных позже, и вызовем np.random.seed(), чтобы сделать примеры с (псевдо) случайными данными воспроизводимыми:

>>> import numpy as np
>>> np.random.seed(444)

Иерархия объектов Matplotlib

Одной из важных концепций matplotlib в целом является иерархия объектов.

Если вы изучали какой-либо вводный учебник по matplotlib, вы, вероятно, вызывали что-то вроде plt.plot([1, 2, 3]). Этот однострочный текст скрывает тот факт, что график на самом деле представляет собой иерархию вложенных объектов Python. “Иерархия” здесь означает, что существует древовидная структура объектов matplotlib, лежащая в основе каждого графика.

Объект Figure является самым внешним контейнером для графического файла matplotlib, который может содержать несколько Axes объектов. Одним из источников путаницы является название: Axes на самом деле переводится как то, что мы считаем отдельным графиком (а не как множественное число от “оси”, как мы могли бы ожидать).

Вы можете представить себе объект Figure как контейнер в виде коробки, содержащий один или несколько Axes (реальных графиков). Ниже Axes в иерархии расположены объекты меньшего размера, такие как деления, отдельные строки, условные обозначения и текстовые поля. Почти каждый “элемент” диаграммы является собственным управляемым объектом Python, вплоть до отметок и меток:

Chart

Вот иллюстрация этой иерархии в действии. Не волнуйтесь, если вы не совсем знакомы с этим обозначением, о котором мы расскажем позже:

>>> fig, _ = plt.subplots()
>>> type(fig)
<class 'matplotlib.figure.Figure'>

Выше мы создали две переменные с помощью plt.subplots(). Первая - это объект верхнего уровня Figure. Вторая - это “одноразовая” переменная, которая нам пока не нужна и обозначается символом подчеркивания. Используя обозначения атрибутов, легко перейти вниз по иерархии рисунков и увидеть первую отметку оси y первого объекта Axes:

>>> one_tick = fig.axes[0].yaxis.get_major_ticks()[0]
>>> type(one_tick)
<class 'matplotlib.axis.YTick'>

Выше, fig (экземпляр класса Figure) содержит несколько Axes (список, для которого мы берем первый элемент). У каждого Axes есть yaxis и xaxis, у каждого из которых есть набор “основных тиков”, и мы выбираем первый из них.

Matplotlib представляет это как анатомию фигуры, а не явную иерархию:

Chart: anatomy of a figure

(В истинном стиле matplotlib приведенный выше рисунок создан в документации matplotlib здесь.)

Подходы с сохранением состояния по сравнению с подходами без сохранения состояния

Хорошо, нам нужен еще один теоретический материал, прежде чем мы перейдем к блестящим визуализациям: разница между stateful (основанным на состоянии, автоматом состояний) и stateless (объектно-ориентированным, OO) интерфейсы.

Выше мы использовали import matplotlib.pyplot as plt для импорта модуля pyplot из matplotlib и присвоения ему имени plt.

Почти все функции из pyplot, такие как plt.plot(), неявно либо ссылаются на существующую текущую фигуру и текущие оси, либо создают их заново, если таковых не существует. В документации matplotlib спрятан этот полезный фрагмент:

“[В pyplot] используются простые функции для добавления элементов графика (линий, изображений, текста и т.д.) к текущим осям на текущем рисунке.” [ выделено мной]

Закоренелые пользователи, ранее работавшие в MATLAB, могут сформулировать это примерно так: “plt.plot() - это интерфейс конечного автомата, который неявно отслеживает текущее значение!” На английском это означает, что:

  • Интерфейс с отслеживанием состояния выполняет свои вызовы с помощью plt.plot() и других функций pyplot верхнего уровня. В данный момент времени вы манипулируете только одной фигурой или осями, и вам не нужно явно ссылаться на нее.
  • Прямое изменение базовых объектов - это объектно-ориентированный подход. Обычно мы делаем это, вызывая методы объекта Axes, который является объектом, представляющим сам график.

Ход этого процесса на высоком уровне выглядит следующим образом:

Flow

Связывая их воедино, можно сказать, что большинство функций из pyplot также существуют как методы класса matplotlib.axes.Axes.

Это легче увидеть, заглянув под капот. plt.plot() может быть сведено примерно к пяти строкам кода:

# matplotlib/pyplot.py
>>> def plot(*args, **kwargs):
...     """An abridged version of plt.plot()."""
...     ax = plt.gca()
...     return ax.plot(*args, **kwargs)

>>> def gca(**kwargs):
...     """Get the current Axes of the current Figure."""
...     return plt.gcf().gca(**kwargs)

Вызов plt.plot() - это просто удобный способ получить текущие оси текущей фигуры и затем вызвать ее метод plot(). Именно это подразумевается под утверждением, что интерфейс с отслеживанием состояния всегда “неявно отслеживает” график, на который он хочет ссылаться.

в pyplot есть набор функций, которые на самом деле являются просто оболочками объектно-ориентированного интерфейса matplotlib. Например, для plt.title() существуют соответствующие методы установки и получения в рамках подхода OO, ax.set_title() и ax.get_title(). (Использование геттеров и сеттеров, как правило, более популярно в таких языках, как Java, но это ключевая особенность OO-подхода matplotlib.)

Вызов plt.title() преобразуется в одну строку: gca().set_title(s, *args, **kwargs). Вот что это делает:

  • gca() захватывает текущую ось и возвращает ее.
  • set_title() это метод настройки, который задает заголовок для этого объекта Axes. “Удобство” здесь в том, что нам не нужно было явно указывать какой-либо объект Axes с помощью plt.title().

Аналогично, если вы уделите несколько минут изучению исходного кода для функций верхнего уровня, таких как plt.grid(), plt.legend(), и plt.ylabels(),, вы заметите, что все они следуют одной и той же структуре делегирования текущим осям с помощью gca(), а затем вызов некоторого метода для текущих осей. (Это базовый объектно-ориентированный подход!)

Понимание plt.subplots() Обозначений

Ладно, хватит теории. Теперь мы готовы связать все воедино и составить график. С этого момента мы будем в основном полагаться на объектно-ориентированный подход без учета состояния, который является более настраиваемым и пригодится по мере усложнения графиков.

Предписанным способом создания фигуры с одной осью в рамках подхода OO является (не слишком интуитивно) использование plt.subplots(). Это действительно единственный случай, когда ОО-подход использует pyplot для создания фигуры и осей:

>>> fig, ax = plt.subplots()

Выше мы воспользовались преимуществами итеративной распаковки, чтобы присвоить отдельную переменную каждому из двух результатов plt.subplots(). Обратите внимание, что здесь мы не передавали аргументы в subplots(). По умолчанию используется вызов subplots(nrows=1, ncols=1). Следовательно, ax является единственным объектом AxesSubplot:

>>> type(ax)
<class 'matplotlib.axes._subplots.AxesSubplot'>

Мы можем вызывать методы его экземпляра для управления графиком аналогично тому, как мы вызываем функции pyplots. Давайте проиллюстрируем это на примере графика трех временных рядов с наложенной областью:

>>> rng = np.arange(50)
>>> rnd = np.random.randint(0, 10, size=(3, rng.size))
>>> yrs = 1950 + rng

>>> fig, ax = plt.subplots(figsize=(5, 3))
>>> ax.stackplot(yrs, rng + rnd, labels=['Eastasia', 'Eurasia', 'Oceania'])
>>> ax.set_title('Combined debt growth over time')
>>> ax.legend(loc='upper left')
>>> ax.set_ylabel('Total debt')
>>> ax.set_xlim(xmin=yrs[0], xmax=yrs[-1])
>>> fig.tight_layout()

Вот что происходит выше:

  • После создания трех случайных временных рядов мы определили одну фигуру (fig), содержащую одну ось (график, ax).

  • Мы вызываем методы ax непосредственно для создания диаграммы с наложением областей и добавления легенды, заголовка и метки по оси y. При объектно-ориентированном подходе ясно, что все это является атрибутами ax.

  • tight_layout() применяется к объекту Figure в целом для устранения заполнения пробелов.

Chart: debt growth over time

Давайте рассмотрим пример с несколькими вспомогательными участками (осями) на одном рисунке, построив два коррелированных массива, которые взяты из дискретного равномерного распределения :

>>> x = np.random.randint(low=1, high=11, size=50)
>>> y = x + np.random.randint(1, 5, size=x.size)
>>> data = np.column_stack((x, y))

>>> fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2,
...                                figsize=(8, 4))

>>> ax1.scatter(x=x, y=y, marker='o', c='r', edgecolor='b')
>>> ax1.set_title('Scatter: $x$ versus $y$')
>>> ax1.set_xlabel('$x$')
>>> ax1.set_ylabel('$y$')

>>> ax2.hist(data, bins=np.arange(data.min(), data.max()),
...          label=('x', 'y'))
>>> ax2.legend(loc=(0.65, 0.8))
>>> ax2.set_title('Frequencies of $x$ and $y$')
>>> ax2.yaxis.tick_right()
Charts

В этом примере есть еще кое-что:

  • Поскольку мы создаем фигуру “1х2”, возвращаемый результат plt.subplots(1, 2) теперь представляет собой объект Figure и массив NumPy объектов Axes. (Вы можете проверить это с помощью fig, axs = plt.subplots(1, 2) и взглянуть на axs.)

  • Мы рассматриваем ax1 и ax2 по отдельности, что было бы трудно сделать при использовании подхода с отслеживанием состояния. Последняя строка является хорошей иллюстрацией иерархии объектов, в которой мы изменяем yaxis, принадлежащий ко второй оси, размещая его галочки и метки для отметок справа.

  • В тексте внутри знаков доллара используется Текстовая разметка для выделения переменных курсивом.

Помните, что несколько осей могут быть заключены в данную фигуру или “принадлежать” ей. В приведенном выше случае fig.axes возвращает нам список всех объектов Axes:

>>> (fig.axes[0] is ax1, fig.axes[1] is ax2)
(True, True)

(fig.axes пишется строчными буквами, а не прописными. Нельзя отрицать, что терминология немного запутанная.)

Сделав еще один шаг вперед, мы могли бы в качестве альтернативы создать фигуру, которая содержит сетку размером 2х2 из Axes объектов:

>>> fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(7, 7))

Теперь, что такое ax? Это уже не один Axes, а их двумерный массив:

>>> type(ax)
numpy.ndarray

>>> ax
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x1106daf98>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x113045c88>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x11d573cf8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x1130117f0>]],
      dtype=object)

>>> ax.shape
(2, 2)

Это подтверждается строкой документации:

ax может быть либо одним matplotlib.axes.Axes объектом, либо массивом из Axes объектов, если было создано более одного подзаголовка.”

Теперь нам нужно вызвать методы построения для каждого из этих Axes (но не для массива NumPy, который в данном случае является просто контейнером). Распространенным способом решения этой проблемы является использование итеративной распаковки после выравнивания массива до одномерного:

>>> fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(7, 7))
>>> ax1, ax2, ax3, ax4 = ax.flatten()  # flatten a 2d NumPy array to 1d

Мы могли бы также сделать это с помощью ((ax1, ax2), (ax3, ax4)) = ax, но первый подход, как правило, более гибкий.

Чтобы проиллюстрировать некоторые дополнительные возможности подзаголовка, давайте возьмем некоторые макроэкономические данные по жилью в Калифорнии, извлеченные из сжатого архива tar, используя io, tarfile, и urllib из стандартной библиотеки Python.

>>> from io import BytesIO
>>> import tarfile
>>> from urllib.request import urlopen

>>> url = 'http://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.tgz'
>>> b = BytesIO(urlopen(url).read())
>>> fpath = 'CaliforniaHousing/cal_housing.data'

>>> with tarfile.open(mode='r', fileobj=b) as archive:
...     housing = np.loadtxt(archive.extractfile(fpath), delimiter=',')

Приведенная ниже переменная “отклик” y, если использовать статистический термин, представляет собой среднюю стоимость жилья в регионе. pop и age - численность населения района и средний возраст жителей, проживающих в доме, соответственно:

>>> y = housing[:, -1]
>>> pop, age = housing[:, [4, 7]].T

Далее давайте определим “вспомогательную функцию”, которая помещает текстовое поле внутри графика и действует как “заголовок на графике”:

>>> def add_titlebox(ax, text):
...     ax.text(.55, .8, text,
...         horizontalalignment='center',
...         transform=ax.transAxes,
...         bbox=dict(facecolor='white', alpha=0.6),
...         fontsize=12.5)
...     return ax

Мы готовы приступить к построению графика. Модуль Matplotlib gridspec позволяет дополнительно настраивать подзаголовок. subplot2grid() в pyplot прекрасно взаимодействует с этим модулем. Допустим, мы хотим создать макет, подобный этому:

Empty gridspec

Выше показано, что на самом деле у нас есть сетка размером 3х2. ax1 в два раза больше высоты и ширины ax2/ax3,, что означает, что он занимает два столбца и две строки.

Empty gridspec

Вторым аргументом для subplot2grid() является расположение осей (строк, столбцов) в сетке:

>>> gridsize = (3, 2)
>>> fig = plt.figure(figsize=(12, 8))
>>> ax1 = plt.subplot2grid(gridsize, (0, 0), colspan=2, rowspan=2)
>>> ax2 = plt.subplot2grid(gridsize, (2, 0))
>>> ax3 = plt.subplot2grid(gridsize, (2, 1))

Теперь мы можем продолжить в обычном режиме, изменяя каждую ось по отдельности:

>>> ax1.set_title('Home value as a function of home age & area population',
...               fontsize=14)
>>> sctr = ax1.scatter(x=age, y=pop, c=y, cmap='RdYlGn')
>>> plt.colorbar(sctr, ax=ax1, format='$%d')
>>> ax1.set_yscale('log')
>>> ax2.hist(age, bins='auto')
>>> ax3.hist(pop, bins='auto', log=True)

>>> add_titlebox(ax2, 'Histogram: home age')
>>> add_titlebox(ax3, 'Histogram: area population (log scl.)')
Charts

Выше, colorbar() (в отличие от ColorMap ранее) вызывается непосредственно на рисунке, а не на осях. Его первый аргумент использует .scatter() из Matplotlib и является результатом ax1.scatter(), который функционирует как отображение значений y в цветовую карту.

Визуально, при перемещении вверх и вниз по оси y цвет (переменная y) практически не различается, что указывает на то, что возраст дома, по-видимому, является более сильным фактором, определяющим стоимость дома.

“Фигуры” за кулисами

Каждый раз, когда вы вызываете plt.subplots() или менее часто используемый plt.figure() (который создает фигуру без осей), вы создаете новый объект Figure, который matplotlib тайно хранит в памяти. Ранее мы упоминали концепцию текущей фигуры и текущих осей. По умолчанию это самые последние созданные фигуры и оси, которые мы можем отобразить с помощью встроенной функции id() для отображения адреса объекта в памяти:

>>> fig1, ax1 = plt.subplots()

>>> id(fig1)
4525567840

>>> id(plt.gcf())  # `fig1` is the current figure.
4525567840

>>> fig2, ax2 = plt.subplots()
>>> id(fig2) == id(plt.gcf())  # The current figure has changed to `fig2`.
True

(Здесь мы могли бы также использовать встроенный оператор is.)

После выполнения описанной выше процедуры текущей фигурой будет fig2, самая последняя созданная фигура. Однако обе цифры все еще хранятся в памяти, каждая с соответствующим идентификационным номером (индексированным на 1, в стиле MATLAB):

>>> plt.get_fignums()
[1, 2]

Полезный способ получить все сами цифры - это сопоставить plt.figure() каждому из этих целых чисел:

>>> def get_all_figures():
...    return [plt.figure(i) for i in plt.get_fignums()]

>>> get_all_figures()
[<matplotlib.figure.Figure at 0x10dbeaf60>,
 <matplotlib.figure.Figure at 0x1234cb6d8>]

Помните об этом, если вы запускаете скрипт, в котором создаете группу фигур. Вам нужно будет явно закрыть каждую из них после использования, чтобы избежать появления MemoryError. Сам по себе plt.close() закрывает текущую фигуру, plt.close(num) закрывает номер фигуры num и plt.close('all') закрывает все окна с фигурами:

>>> plt.close('all')
>>> get_all_figures()
[]

Вспышка цвета: imshow() и matshow()

Хотя ax.plot() является одним из наиболее распространенных методов построения осей, существует также целый ряд других. (Выше мы использовали ax.stackplot(). С полным списком Вы можете ознакомиться здесь.)

Наиболее часто используются методы imshow() и matshow(), причем последний является оболочкой первого. Они полезны в любое время, когда исходный числовой массив можно визуализировать в виде цветной сетки.

Во-первых, давайте создадим две отдельные сетки с некоторой причудливой числовой индексацией:

>>> x = np.diag(np.arange(2, 12))[::-1]
>>> x[np.diag_indices_from(x[::-1])] = np.arange(2, 12)
>>> x2 = np.arange(x.size).reshape(x.shape)

Затем мы можем сопоставить их с их представлениями в виде изображений. В данном конкретном случае мы отключаем все метки и отметки на осях, используя понимание по словарю и передавая результат в ax.tick_params():

>>> sides = ('left', 'right', 'top', 'bottom')
>>> nolabels = {s: False for s in sides}
>>> nolabels.update({'label%s' % s: False for s in sides})
>>> print(nolabels)
{'left': False, 'right': False, 'top': False, 'bottom': False, 'labelleft': False,
 'labelright': False, 'labeltop': False, 'labelbottom': False}

Затем мы можем использовать контекстный менеджер, чтобы отключить сетку, и вызвать matshow() для каждой оси. Наконец, нам нужно поместить цветовую панель в то, что технически является новой осью внутри fig. Для этого мы можем использовать немного эзотерическую функцию из глубины matplotlib:

>>> from mpl_toolkits.axes_grid1.axes_divider import make_axes_locatable

>>> with plt.rc_context(rc={'axes.grid': False}):
...     fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4))
...     ax1.matshow(x)
...     img2 = ax2.matshow(x2, cmap='RdYlGn_r')
...     for ax in (ax1, ax2):
...         ax.tick_params(axis='both', which='both', **nolabels)
...     for i, j in zip(*x.nonzero()):
...         ax1.text(j, i, x[i, j], color='white', ha='center', va='center')
...
...     divider = make_axes_locatable(ax2)
...     cax = divider.append_axes("right", size='5%', pad=0)
...     plt.colorbar(img2, cax=cax, ax=[ax1, ax2])
...     fig.suptitle('Heatmaps with `Axes.matshow`', fontsize=16)
Heat maps

Построение графиков в Pandas

Библиотека pandas стала популярной не только благодаря возможности мощного анализа данных, но и благодаря своим удобным заранее подготовленным методам построения графиков. Интересно, однако, что методы построения графиков pandas на самом деле являются просто удобными оболочками для существующих вызовов matplotlib.

То есть, метод plot() в серии pandas и Фрейм данных - это оболочка для plt.plot(). Например, одно из предоставляемых преимуществ заключается в том, что если индекс фрейма данных состоит из дат, gcf().autofmt_xdate() вызывается pandas для получения текущего значения и автоматического форматирования оси x.

В свою очередь, помните, что plt.plot() (подход, основанный на состоянии) неявно учитывает текущую фигуру и текущие оси, поэтому pandas следует подходу, основанному на состоянии, в более широком смысле.

Мы можем доказать эту “цепочку” вызовов функций с помощью небольшого самоанализа. Во-первых, давайте построим простую серию pandas, предполагая, что мы начинаем с новой сессии интерпретатора:

>>> import pandas as pd

>>> s = pd.Series(np.arange(5), index=list('abcde'))
>>> ax = s.plot()

>>> type(ax)
<matplotlib.axes._subplots.AxesSubplot at 0x121083eb8>

>>> id(plt.gca()) == id(ax)
True

Эту внутреннюю архитектуру полезно знать, когда вы смешиваете методы построения графиков pandas с традиционными вызовами matplotlib, что делается ниже при построении скользящего среднего для широко просматриваемого финансового временного ряда. ma - это серия pandas, для которой мы можем вызвать ma.plot() (метод pandas), а затем настроить, извлекая оси, созданные этим вызовом (plt.gca()), для ссылки на matplotlib:

>>> import pandas as pd
>>> import matplotlib.transforms as mtransforms

>>> url = 'https://fred.stlouisfed.org/graph/fredgraph.csv?id=VIXCLS'
>>> vix = pd.read_csv(url, index_col=0, parse_dates=True, na_values='.',
...                   infer_datetime_format=True,
...                   squeeze=True).dropna()
>>> ma = vix.rolling('90d').mean()
>>> state = pd.cut(ma, bins=[-np.inf, 14, 18, 24, np.inf],
...                labels=range(4))

>>> cmap = plt.get_cmap('RdYlGn_r')
>>> ma.plot(color='black', linewidth=1.5, marker='', figsize=(8, 4),
...         label='VIX 90d MA')
>>> ax = plt.gca()  # Get the current Axes that ma.plot() references
>>> ax.set_xlabel('')
>>> ax.set_ylabel('90d moving average: CBOE VIX')
>>> ax.set_title('Volatility Regime State')
>>> ax.grid(False)
>>> ax.legend(loc='upper center')
>>> ax.set_xlim(xmin=ma.index[0], xmax=ma.index[-1])

>>> trans = mtransforms.blended_transform_factory(ax.transData, ax.transAxes)
>>> for i, color in enumerate(cmap([0.2, 0.4, 0.6, 0.8])):
...     ax.fill_between(ma.index, 0, 1, where=state==i,
...                     facecolor=color, transform=trans)
>>> ax.axhline(vix.mean(), linestyle='dashed', color='xkcd:dark grey',
...            alpha=0.6, label='Full-period mean', marker='')
Volatility Regime State diagram

Выше много чего происходит:

  • ma это 90-дневная скользящая средняя индекса VIX, измеряющая ожидания рынка относительно краткосрочной волатильности акций. state - это привязка скользящей средней к различным режимным состояниям. Считается, что высокий VIX сигнализирует о повышенном уровне страха на рынке.

  • cmap это цветовая карта — объект matplotlib, который по сути представляет собой отображение значений с плавающей точкой в цвета RGBA. Любую цветовую карту можно изменить, добавив '_r', таким образом, 'RdYlGn_r' - это перевернутая красно-желто-зеленая цветовая карта. Matplotlib поддерживает удобное визуальное справочное руководство по цветовым картам в своих документах.

  • Единственный настоящий призыв к пандам, который мы здесь делаем, - это ma.plot(). Это вызывает внутренний вызов plt.plot(), поэтому для интеграции объектно-ориентированного подхода нам нужно получить явную ссылку на текущие оси с помощью ax = plt.gca().

  • Второй фрагмент кода создает блоки, заполненные цветом, которые соответствуют каждой ячейке из state. cmap([0.2, 0.4, 0.6, 0.8]) написано: “Получите последовательность RGBA для цветов в 20-м, 40-м, 60-м и 80-м "процентилях" спектра цветовых карт.” enumerate() это используется потому, что мы хотим сопоставить каждый цвет RGBA с определенным состоянием.

В Pandas также встроено небольшое количество более продвинутых графиков (которые сами по себе могут занять целый учебник). Однако все они, как и их более простые аналоги, внутренне основаны на механизме matplotlib.

Завершение

Как показано в некоторых из приведенных выше примеров, невозможно обойти тот факт, что matplotlib может быть технической библиотекой с большим количеством синтаксиса. Для создания готовой к работе диаграммы иногда требуется полчаса поиска в Google и комбинирования множества линий, чтобы точно настроить график.

Однако понимание того, как взаимодействуют интерфейсы matplotlib, - это инвестиции, которые могут окупиться в будущем. Как посоветовал Дэн Бадер из Real Python, потратить время на анализ кода, а не прибегать к “копированию макаронных изделий” с переполнением стека, как правило, является более разумным долгосрочным решением. Приверженность объектно-ориентированному подходу может избавить от многих часов разочарования, когда вы хотите превратить обычный сюжет в произведение искусства.

Дополнительные ресурсы

Из документации matplotlib:

Бесплатный бонус: Нажмите здесь, чтобы загрузить 5 примеров на Python + Matplotlib с полным исходным кодом , которые вы можете использовать в качестве основы для создания ваших собственных графиков.

Сторонние ресурсы:

Другие библиотеки построения графиков:

  • Библиотека seaborn, созданная поверх matplotlib и предназначенная для расширенной статистической графики, которая сама по себе может занять целый учебник
  • Datashader - графическая библиотека, специально предназначенная для работы с большими наборами данных
  • Список других сторонних пакетов из документации matplotlib

Приложение А: Конфигурация и стиль

Если вы внимательно следили за этим руководством, то, скорее всего, графики, появляющиеся на вашем экране, стилистически выглядят иначе, чем те, что показаны здесь.

Matplotlib предлагает два способа единообразной настройки стиля для разных графиков:

  1. Путем настройки файла matplotlibrc
  2. Изменяя параметры конфигурации в интерактивном режиме или с помощью скрипта .py.

Файл matplotlibrc (вариант № 1 выше) - это, по сути, текстовый файл, содержащий пользовательские настройки, которые запоминаются между сеансами работы с Python. В Mac OS X это обычно находится по адресу ~/.matplotlib/matplotlibrc.

Краткий совет: GitHub - отличное место для хранения файлов конфигурации. Я храню свои здесь. Просто убедитесь, что они не содержат личной информации, позволяющей установить личность, такой как пароли или секретные ключи SSH!

В качестве альтернативы, вы можете изменить параметры конфигурации в интерактивном режиме (вариант №2 выше). При выполнении import matplotlib.pyplot as plt вы получаете доступ к объекту rcParams, который напоминает словарь настроек Python. Все объекты модуля, начинающиеся на “rc”, являются средством взаимодействия с вашими стилями печати и настройками:

>>> [attr for attr in dir(plt) if attr.startswith('rc')]
['rc', 'rcParams', 'rcParamsDefault', 'rc_context', 'rcdefaults']

Из них:

  • plt.rcdefaults() восстанавливает rc-параметры из внутренних значений по умолчанию matplotlib, которые перечислены в plt.rcParamsDefault. Это вернет (перезапишет) все, что вы уже настроили в файле matplotlibrc.
  • plt.rc() используется для интерактивной настройки параметров.
  • plt.rcParams это (изменяемый) словарный объект, который позволяет напрямую манипулировать настройками. Если вы настроили параметры в файле matplotlibrc, они будут отражены в этом словаре.

С plt.rc() и plt.rcParams эти два синтаксиса эквивалентны для настройки параметров:

>>> plt.rc('lines', linewidth=2, color='r')  # Syntax 1

>>> plt.rcParams['lines.linewidth'] = 2  # Syntax 2
>>> plt.rcParams['lines.color'] = 'r'

Примечательно, что класс Figure затем использует некоторые из этих в качестве аргументов по умолчанию.

Соответственно, стиль - это просто предопределенный набор пользовательских настроек. Чтобы просмотреть доступные стили, используйте:

>>> plt.style.available
['seaborn-dark', 'seaborn-darkgrid', 'seaborn-ticks', 'fivethirtyeight',
 'seaborn-whitegrid', 'classic', '_classic_test', 'fast', 'seaborn-talk',
 'seaborn-dark-palette', 'seaborn-bright', 'seaborn-pastel', 'grayscale',
 'seaborn-notebook', 'ggplot', 'seaborn-colorblind', 'seaborn-muted',
 'seaborn', 'Solarize_Light2', 'seaborn-paper', 'bmh', 'seaborn-white',
 'dark_background', 'seaborn-poster', 'seaborn-deep']

Чтобы задать стиль, выполните этот вызов:

>>> plt.style.use('fivethirtyeight')

Теперь ваши графики будут выглядеть по-новому:

Chart

Этот полный пример доступен здесь.

Для вдохновения matplotlib также содержит некоторые таблицы стилей для справки.

Приложение В: Интерактивный режим

За кулисами matplotlib также взаимодействует с различными серверными системами. Серверная часть - это рабочая лошадка, которая фактически отвечает за рендеринг диаграммы. (Например, в популярном дистрибутиве Anaconda серверной частью по умолчанию является Qt5Agg.) Некоторые серверные части являются интерактивными, что означает, что они динамически обновляются и “всплывают” перед пользователем при изменении.

Хотя интерактивный режим по умолчанию выключен, вы можете проверить его состояние с помощью plt.rcParams['interactive'] или plt.isinteractive(), а также включать и выключать его с помощью plt.ion() и plt.ioff(), соответственно:

>>> plt.rcParams['interactive']  # or: plt.isinteractive()
True
>>> plt.ioff()
>>> plt.rcParams['interactive']
False

В некоторых примерах кода вы можете заметить наличие plt.show() в конце фрагмента кода. Основная цель plt.show(), как следует из названия, состоит в том, чтобы фактически “показать” (открыть) рисунок, когда вы работаете в выключенном интерактивном режиме. Другими словами:

  • Если включен интерактивный режим, вам не нужен plt.show(), и изображения будут автоматически появляться и обновляться по мере того, как вы будете ссылаться на них.
  • Если интерактивный режим выключен, вам потребуется plt.show() для отображения рисунка и plt.draw() для обновления графика.

Ниже мы убеждаемся, что интерактивный режим выключен, для чего требуется, чтобы мы вызвали plt.show() после построения самого графика:

>>> plt.ioff()
>>> x = np.arange(-4, 5)
>>> y1 = x ** 2
>>> y2 = 10 / (x ** 2 + 1)
>>> fig, ax = plt.subplots()
>>> ax.plot(x, y1, 'rx', x, y2, 'b+', linestyle='solid')
>>> ax.fill_between(x, y1, y2, where=y2>y1, interpolate=True,
...                 color='green', alpha=0.3)
>>> lgnd = ax.legend(['y1', 'y2'], loc='upper center', shadow=True)
>>> lgnd.get_frame().set_facecolor('#ffb19a')
>>> plt.show()

Примечательно, что интерактивный режим не имеет ничего общего с тем, какую среду IDE вы используете, или с тем, включили ли вы встроенную графику с помощью чего-то вроде jupyter notebook --matplotlib inline или %matplotlib.

<статус завершения article-slug="python-matplotlib-руководство" class="btn-group mb-0" data-api-article-bookmark-url="/api/v1/articles/python-matplotlib-руководство/закладка/" data-api-article-завершение-статус-url="/api/версия 1/статьи/python-matplotlib-руководство/завершение_статуса/"> <кнопка поделиться bluesky-text="Интересная статья на #Python от @realpython.com :" email-body="Ознакомьтесь с этой статьей о Python:%0A%0APython Построение графиков с помощью Matplotlib (руководство)" email-subject="Статья о Python для вас" twitter-text="Интересная статья о #Python от @realpython:" url="https://realpython.com/python-matplotlib-guide /" url-title="Построение графиков на Python с помощью Matplotlib (руководство)">

Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Построение графиков на Python с помощью Matplotlib

Back to Top