使用 Matplotlib 进行 Python 绘图指南
目录
- Why Can Matplotlib Be Confusing?
- Pylab: What Is It, and Should I Use It?
- The Matplotlib Object Hierarchy
- Stateful Versus Stateless Approaches
- Understanding plt.subplots() Notation
- The “Figures” Behind The Scenes
- A Burst of Color: imshow() and matshow()
- Plotting in Pandas
- Wrapping Up
- More Resources
- Appendix A: Configuration and Styling
- Appendix B: Interactive Mode
一张图片值一千字,而使用 Python 的matplotlib库,幸运的是,只需不到一千字的代码就可以创建出具有生产质量的图形。
然而,matplotlib 也是一个庞大的库,让一个情节看起来恰到好处通常是通过反复试验来实现的。在 matplotlib 中使用 one-liners 生成基本绘图相当简单,但巧妙地控制库中剩余的 98% 可能会令人生畏。
本文是 matplotlib 的初级到中级演练,将理论与示例相结合。虽然通过实例学习可以非常有见地,但它甚至有助于对图书馆的内部运作和布局有一个表面的理解。
以下是我们将介绍的内容:
- Pylab 和 pyplot:哪个是哪个?
- matplotlib 设计的关键概念
- 理解
plt.subplots()
- 使用 matplotlib 可视化数组
- 使用 pandas + matplotlib 组合绘图
本文假设用户对 NumPy 有一点了解。我们将主要使用该numpy.random
模块生成“玩具”数据,从不同的统计分布中抽取样本。
如果您尚未安装 matplotlib,请在继续之前查看此处的演练。
Why Can Matplotlib Be Confusing?
学习 matplotlib 有时可能是一个令人沮丧的过程。问题不在于缺少 matplotlib 的文档:文档实际上非常广泛。但以下问题可能会带来一些挑战:
- 库本身很大,大约有 70,000 行代码。
- Matplotlib 拥有多种不同的界面(构建图形的方式),并且能够与少数不同的后端进行交互。(后端处理图表实际呈现的过程,而不仅仅是内部结构化。)
- 虽然它很全面,但 matplotlib 自己的一些公共文档已经严重过时了。该库仍在不断发展,许多在网上流传的旧示例在其现代版本中可能会减少 70% 的代码行。
因此,在我们进入任何华丽的例子之前,掌握 matplotlib 设计的核心概念是很有用的。
Pylab: What Is It, and Should I Use It?
让我们从一段历史开始:神经生物学家 John D. Hunter 于 2003 年左右开始开发 matplotlib,最初的灵感来自于模拟 Mathworks 的MATLAB软件中的命令。John 在 2012 年 44 岁时不幸去世,matplotlib 现在是一个成熟的社区项目,由许多其他人开发和维护。(约翰给一个谈话约matplotlib在2012年SciPy的会议,这是值得关注的变化。)
MATLAB 的一项相关特性是其全局样式。导入的 Python 概念在 MATLAB 中并没有大量使用,并且 MATLAB 的大部分功能在顶层对用户来说是现成的。
知道 matplotlib 起源于 MATLAB 有助于解释为什么 pylab 存在。pylab 是 matplotlib 库中的一个模块,旨在模仿 MATLAB 的全局风格。它的存在只是为了将 NumPy 和 matplotlib 中的许多函数和类带入命名空间,使不习惯需要import
语句的前 MATLAB 用户轻松过渡。
前 MATLAB 转换者(我保证他们都是好人,我保证!)喜欢这个功能,因为使用from pylab import *
,他们可以像在 MATLAB 中一样简单地调用plot()
或array()
直接调用。
这里的问题对一些 Python 用户来说可能很明显:from pylab import *
在会话或脚本中使用通常是不好的做法。Matplotlib 现在在其自己的教程中直接建议不要这样做:
“由于历史原因,[pylab] 仍然存在,但强烈建议不要使用。它使用会影响 Python 内置函数的函数污染命名空间,并可能导致难以跟踪的错误。要在没有导入的情况下获得 IPython 集成,最好使用
%matplotlib
魔法。” [来源]
在内部,在简短的 pylab源代码中隐藏了大量潜在的冲突导入。事实上,使用ipython --pylab
(从终端/命令行)或%pylab
(从 IPython/Jupyter 工具)只是from pylab import *
在幕后调用。
底线是matplotlib 已经放弃了这个方便的模块,现在明确建议不要使用 pylab,使事情更符合 Python 的一个关键概念:显式优于隐式。
不需要 pylab,我们通常可以只使用一个规范导入:
>>> import matplotlib.pyplot as plt
在此过程中,我们还导入 NumPy,稍后我们将使用它来生成数据,并调用np.random.seed()
可重现(伪)随机数据的示例:
>>> import numpy as np
>>> np.random.seed(444)
The Matplotlib Object Hierarchy
matplotlib 的一个重要概念是它的对象层次结构。
如果您已经学习过任何介绍性的 matplotlib 教程,那么您可能已经调用过类似plt.plot([1, 2, 3])
. 这个单行隐藏了一个事实,即绘图实际上是嵌套 Python 对象的层次结构。这里的“层次结构”意味着每个图下面都有一个 matplotlib 对象的树状结构。
甲Figure
对象为matplotlib图形,它可以包含多个最外容器Axes
对象。混淆的一个来源是名称:anAxes
实际上转化为我们认为的单个情节或图形(而不是我们所期望的“轴”的复数形式)。
您可以将Figure
对象视为装有一个或多个Axes
(实际绘图)的盒状容器。Axes
层次结构中的下方是较小的对象,例如刻度线、单行、图例和文本框。图表的几乎每个“元素”都是它自己的可操作 Python 对象,一直到刻度和标签:
这是此层次结构的示例图。如果您不完全熟悉这种表示法,请不要担心,我们将在稍后介绍:
>>> fig, _ = plt.subplots()
>>> type(fig)
<class 'matplotlib.figure.Figure'>
上面,我们创建了两个变量有plt.subplots()
。第一个是顶级Figure
对象。第二个是我们还不需要的“一次性”变量,用下划线表示。使用属性表示法,很容易向下遍历图形层次结构并查看第一个 Axes 对象的 y 轴的第一个刻度:
>>> one_tick = fig.axes[0].yaxis.get_major_ticks()[0]
>>> type(one_tick)
<class 'matplotlib.axis.YTick'>
上面,fig
(一个Figure
类实例)有多个Axes
(一个列表,我们取第一个元素)。每个Axes
都有一个yaxis
and xaxis
,每个都有一个“主要刻度”的集合,我们抓住第一个。
Matplotlib 将其呈现为人体解剖结构,而不是明确的层次结构:
(在真正的 matplotlib 样式中,上图是在此处的 matplotlib 文档中创建的。)
Stateful Versus Stateless Approaches
好吧,在我们开始讨论闪亮的可视化之前,我们还需要更多的理论知识:有状态(基于状态,状态机)和无状态(面向对象,OO)接口之间的区别。
上面,我们曾经import matplotlib.pyplot as plt
从 matplotlib 导入 pyplot 模块并将其命名为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()
只是获取当前 Figure 的当前 Axes 然后调用其plot()
方法的便捷方式。这就是有状态接口总是“隐式跟踪”它想要引用的图的断言的意思。
pyplot 是一批函数的所在地,这些函数实际上只是围绕 matplotlib 的面向对象接口的包装。例如,with plt.title()
,OO 方法中有相应的 setter 和 getter 方法,ax.set_title()
以及ax.get_title()
。(getter 和 setter 的使用在Java等语言中更受欢迎,但它是 matplotlib 的 OO 方法的一个关键特性。)
呼叫plt.title()
被翻译成这一行:gca().set_title(s, *args, **kwargs)
。这是在做什么:
gca()
抓取当前轴并返回它。set_title()
是设置该 Axes 对象的标题的 setter 方法。这里的“方便”是我们不需要用 明确指定任何 Axes 对象plt.title()
。
类似地,如果您花一些时间查看诸如plt.grid()
,plt.legend()
和 之类的顶级函数的源代码plt.ylabels()
,您会注意到它们都遵循相同的结构,即委托给当前 Axes withgca()
然后调用当前 Axes 的某些方法轴。(这是底层的面向对象方法!)
Understanding plt.subplots()
Notation
好了,理论够了。现在,我们已准备好将所有内容联系在一起并进行一些绘图。从现在开始,我们将主要依赖无状态(面向对象)方法,这种方法更具可定制性,并且随着图形变得更加复杂而派上用场。
在 OO 方法下创建具有单个轴的图形的规定方法是(不太直观)使用plt.subplots()
. 这确实是 OO 方法唯一一次使用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()
在这个例子中还有一些事情要做:
-
因为我们正在创建一个“1x2”图形,返回的结果
plt.subplots(1, 2)
现在是一个 Figure 对象和一个 Axes 对象的 NumPy 数组。(您可以使用fig, axs = plt.subplots(1, 2)
并查看 来检查它axs
。) -
我们单独处理
ax1
和处理ax2
,这很难用有状态的方法来处理。最后一行很好地说明了对象层次结构,我们正在修改yaxis
属于第二个轴的对象,将其刻度和刻度标签放在右侧。 -
美元符号内的文本利用TeX 标记将变量置于斜体中。
请记住,多个轴可以包含在或“属于”给定图形中。在上面的例子中,fig.axes
给我们一个所有 Axes 对象的列表:
>>> (fig.axes[0] is ax1, fig.axes[1] is ax2)
(True, True)
(fig.axes
是小写,而不是大写。不可否认,术语有点令人困惑。)
更进一步,我们也可以创建一个包含 2x2Axes
对象网格的图形:
>>> fig, ax = plt.subplots(nrows=2, ncols=2, figsize=(7, 7))
现在,什么是ax
?它不再是单个Axes
,而是它们的二维 NumPy 数组:
>>> 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
模块允许更多的子图定制。pyplotsubplot2grid()
与这个模块很好地交互。假设我们要创建这样的布局:
上面,我们实际拥有的是一个 3x2 的网格。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 不同)直接在 Figure 上调用,而不是在 Axes 上调用。它的第一个参数使用 Matplotlib 的,.scatter()
并且是 的结果ax1.scatter()
,它用作 y 值到 ColorMap 的映射。
从视觉上看,当我们沿 y 轴上下移动时,颜色(y 变量)没有太大差异,这表明房屋年龄似乎是房屋价值的更强决定因素。
The “Figures” Behind The Scenes
每次调用plt.subplots()
或不常用的plt.figure()
(创建一个没有轴的图形)时,您都在创建一个新的图形对象,matplotlib 偷偷地将它保存在内存中。早些时候,我们提到了当前图形和当前轴的概念。默认情况下,这些是最近创建的 Figure 和 Axes,我们可以使用内置函数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
,最近创建的图形。然而,这两个数字仍然在内存中徘徊,每个数字都有一个对应的 ID 号(1-indexed,在 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()
[]
A Burst of Color: imshow()
and matshow()
虽然ax.plot()
是 Axes 上最常见的绘图方法之一,但还有许多其他方法。(我们在ax.stackplot()
上面使用过。您可以在此处找到完整列表。)
大量使用的方法是imshow()
and matshow()
,后者是前者的包装器。这些在任何时候都非常有用,可以将原始数值数组可视化为彩色网格。
首先,让我们用一些奇特的 NumPy 索引创建两个不同的网格:
>>> 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)
Plotting in Pandas
pandas 库不仅因为支持强大的数据分析,还因为它方便的预制绘图方法而变得流行。有趣的是,pandas 绘图方法实际上只是对现有 matplotlib 调用的方便包装。
也就是说,plot()
pandas 的 Series 和DataFrame上的方法是plt.plot()
. 例如,提供的一种便利是,如果 DataFrame 的索引由日期组成,则由 Pandas 在gcf().autofmt_xdate()
内部调用以获取当前图形并很好地自动格式化 x 轴。
反过来,请记住plt.plot()
(基于状态的方法)隐式地知道当前的图形和当前的轴,因此熊猫通过扩展遵循基于状态的方法。
我们可以通过一些内省来证明这个函数调用的“链”。首先,让我们构建一个普通的熊猫系列,假设我们从一个新的解释器会话开始:
>>> 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方法),然后通过检索这个调用创建的Axes进行自定义(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
是 VIX 指数的 90 天移动平均线,衡量近期股票波动的市场预期。state
是将移动平均线划分为不同的状态状态。高 VIX 被视为表明市场恐惧程度加剧。 -
cmap
是一个 ColorMap——一个 matplotlib 对象,它本质上是一个浮点数到 RGBA 颜色的映射。任何颜色图都可以通过附加来反转'_r'
,'RdYlGn_r'
反转的红黄绿颜色图也是如此。Matplotlib在其文档中维护了一个方便的ColorMaps视觉参考指南。 -
我们在这里发出的唯一真正的熊猫电话是
ma.plot()
. 这在plt.plot()
内部调用,因此为了集成面向对象的方法,我们需要使用 获得对当前 Axes 的显式引用ax = plt.gca()
。 -
第二段代码创建了对应于
state
.cmap([0.2, 0.4, 0.6, 0.8])
说,“给我们一个 RGBA 序列,用于在 ColorMaps 光谱中第 20、40、60 和 80 个‘百分位数’的颜色。”enumerate()
之所以使用,是因为我们想将每个 RGBA 颜色映射回一个状态。
Pandas 还内置了一些更高级的情节(这些情节可以单独占用整个教程)。然而,所有这些,就像它们更简单的对应物一样,在内部依赖于 matplotlib 机制。
Wrapping Up
正如上面的一些示例所示,matplotlib 可能是一个技术性的、语法繁重的库这一事实是无可避免的。创建可用于生产的图表有时需要半小时的谷歌搜索并结合一大堆线条来微调情节。
然而,了解 matplotlib 的接口如何交互是一项可以在未来获得回报的投资。正如 Real Python 自己的 Dan Bader 所建议的那样,花时间剖析代码而不是求助于 Stack Overflow“复制面食”解决方案往往是一个更聪明的长期解决方案。当您想将一个情节从简单的情节变为艺术作品时,坚持面向对象的方法可以节省数小时的挫败感。
更多资源
来自 matplotlib 文档:
第三方资源:
- DataCamp 的 matplotlib备忘单
- PLOS 计算生物学:获得更好数字的十个简单规则
- Wes McKinney 的Python for Data Analysis第 9 章(绘图和可视化),第 2 版。
- Ted Petrou 的Pandas Cookbook第 11 章(使用 Matplotlib、Pandas 和 Seaborn 进行可视化)
- SciPy 讲义的第 1.4 节(Matplotlib:绘图)
- 该XKCD调色板
- matplotlib外部资源页面
- Matplotlib、Pylab、Pyplot 等:它们之间有什么区别以及何时使用它们?来自 queirozf.com
- pandas 文档中的可视化页面
其他绘图库:
- 该seaborn库,建立在matplotlib的顶部,设计先进的统计图形,这可能需要长达一整个教程的所有对自己
- Datashader,一个专门针对大型数据集的图形库
- 来自 matplotlib 文档的其他第三方包的列表
Appendix A: Configuration and Styling
如果您一直在学习本教程,则屏幕上弹出的图形可能与此处显示的图形在风格上有所不同。
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()
从 matplotlib 的内部默认值中恢复 rc 参数,这些参数在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 还保留了一些样式表显示以供参考。
Appendix B: Interactive Mode
在幕后,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
。
- 点赞
- 收藏
- 关注作者
评论(0)