Построение графиков на Python с помощью Matplotlib (Руководство)
Оглавление
- Почему Matplotlib Может Сбивать С Толку?
- Pylab: Что Это такое и должен ли я его использовать?
- Иерархия объектов Matplotlib
- Подходы с сохранением состояния по сравнению с подходами без сохранения состояния
- Понимание нотации plt.subplots()
- “Деятели” За кулисами
- Всплеск цвета: imshow() и matshow()
- Заговор у панд
- Сворачивание
- Больше ресурсов
- Приложение А: Конфигурация и стиль
- Приложение В: Интерактивный режим
Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой 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, вплоть до отметок и меток:
Вот иллюстрация этой иерархии в действии. Не волнуйтесь, если вы не совсем знакомы с этим обозначением, о котором мы расскажем позже:
>>> 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 представляет это как анатомию фигуры, а не явную иерархию:
(В истинном стиле 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, который является объектом, представляющим сам график.
Ход этого процесса на высоком уровне выглядит следующим образом:
Связывая их воедино, можно сказать, что большинство функций из 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 в целом для устранения заполнения пробелов.
Давайте рассмотрим пример с несколькими вспомогательными участками (осями) на одном рисунке, построив два коррелированных массива, которые взяты из дискретного равномерного распределения :
>>> 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()
кодовый блок>
В этом примере есть еще кое-что:
-
Поскольку мы создаем фигуру “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 прекрасно взаимодействует с этим модулем. Допустим, мы хотим создать макет, подобный этому:
Выше показано, что на самом деле у нас есть сетка размером 3х2. ax1 в два раза больше высоты и ширины ax2/ax3,, что означает, что он занимает два столбца и две строки.
Вторым аргументом для 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.)')
кодовый блок>
Выше, 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)
кодовый блок>
Построение графиков в 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='')
кодовый блок>
Выше много чего происходит:
-
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:
- Индекс matplotlib Примеры
- Использование Часто задаваемые вопросы
- Страница учебных пособий, которая разделена на разделы для начинающих, среднего и продвинутого уровня
- Жизненный цикл графика, в котором рассматриваются подходы объектно-ориентированный и с учетом состояния
Бесплатный бонус: Нажмите здесь, чтобы загрузить 5 примеров на Python + Matplotlib с полным исходным кодом , которые вы можете использовать в качестве основы для создания ваших собственных графиков.
Сторонние ресурсы:
- matplotlib из DataCamp шпаргалка
- Вычислительная биология PLOS: Десять простых правил для улучшения показателей
- Глава 9 (Построение графиков и визуализация) книги Уэса Маккинни Python для анализа данных, 2-е изд..
- Глава 11 (Визуализация с помощью Matplotlib, Pandas и Seaborn) из книги Теда Петру Кулинарная книга о пандах
- Раздел 1.4 (Matplotlib: построение графиков) из конспекта лекции SciPy
- Цветовая палитра xkcd
- Библиотека matplotlib внешние ресурсы страница
- Matplotlib, Pylab, Pyplot и т.д.: В чем разница между ними и когда использовать каждый из них? из queirozf.com
- Страница визуализации в документации pandas
Другие библиотеки построения графиков:
- Библиотека seaborn, созданная поверх matplotlib и предназначенная для расширенной статистической графики, которая сама по себе может занять целый учебник
- Datashader - графическая библиотека, специально предназначенная для работы с большими наборами данных
- Список других сторонних пакетов из документации matplotlib
Приложение А: Конфигурация и стиль
Если вы внимательно следили за этим руководством, то, скорее всего, графики, появляющиеся на вашем экране, стилистически выглядят иначе, чем те, что показаны здесь.
Matplotlib предлагает два способа единообразной настройки стиля для разных графиков:
- Путем настройки файла matplotlibrc
- Изменяя параметры конфигурации в интерактивном режиме или с помощью скрипта .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')
кодовый блок>
Теперь ваши графики будут выглядеть по-новому:
Этот полный пример доступен здесь.
Для вдохновения 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.
Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Построение графиков на Python с помощью Matplotlib
Back to Top