8.3 Matplotlib 可视化
原文:Visualization with Matplotlib
译者:飞龙
本节是《Python 数据科学手册》(Python Data Science Handbook)的摘录。
我们现在将深入研究M atplotlib 包,以便在 Python 中进行可视化。Matplotlib 是一个基于 NumPy 数组的多平台数据可视化库,旨在兼容更广泛的 SciPy 技术栈。它由 John Hunter 在 2002 年构思,最初是作为 IPython 的补丁,用于通过来自 IPython 命令行的gnuplot
实现 MATLAB 风格的交互式绘图。
IPython 的创始人 Fernando Perez 当时正努力完成他的博士学位,并且 John 知道他几个月都没时间审查补丁。John 把它看做一个使自己开始的动机,之后 Matplotlib 软件包诞生了,2003 年发布了 0.1 版本。当它被用作太空望远镜科学研究所(哈勃望远镜背后的人)选择的绘图包时,它得到了早期的赞助,该研究所在财务上支持了 Matplotlib 的开发,并极大地扩展了它的能力。
Matplotlib 最重要的功能之一是,它能够很好地兼容许多操作系统和图形后端。Matplotlib 支持许多后端和输出类型,这意味着无论你使用哪种操作系统或你希望使用哪种输出格式,你都可以依赖它。
这种跨平台的通用方法是 Matplotlib 的强大优势之一。它带来了庞大的用户群,这反过来又产生了活跃的开发人员的基础,和 Python 科学世界中的 Matplotlib 的威力和普遍性。
然而,近年来,Matplotlib 的界面和风格已经有些过时。
比起 R 语言中的ggplot
和ggvis
等新工具,以及基于 D3js 和 HTML5 画布的 Web 可视化工具包,Matplotlib 显得笨重和陈旧。尽管如此,我认为我们不能忽视 Matplotlib 作为经过良好测试的跨平台图形引擎的优势。
最近的 Matplotlib 版本,使得设置新的全局绘图样式变得相对容易(参见“自定义 Matplotlib:配置和样式表”),人们一直在开发新的包,它们基于 Matplotlib 的强大内核,通过更清洁,更现代的 API 驱动 Matplotlib - 例如,Seaborn(“可视化与 Seaborn”),ggpy,HoloViews,Altair,甚至 Pandas 本身都可以用作 Matplotlib API 的包装。
即使使用这样的包装器,通常也可以深入研究 Matplotlib 的语法来调整最终的绘图输出。出于这个原因,我相信 Matplotlib 本身,仍将是数据可视化技术栈的重要组件,即使新工具意味着社区逐渐不再使用 Matplotlib API。
一般 Matplotlib 提示
在我们深入了解使用 Matplotlib 创建可视化的细节之前,你应该了解一些使用该软件包的有用信息。
导入 Matplotlib
正如我们对 NumPy 使用np
简写,对 Pandas 使用pd
简写一样,我们将对 Matplotlib 导入使用一些标准简写:
import matplotlib as mpl
import matplotlib.pyplot as plt
我们将经常使用plt
接口,我们将在本章中看到。
设置样式
我们将使用plt.style
指令,为我们的图形选择合适的美学风格。在这里,我们将设置classic
样式,确保我们创建的图使用经典的 Matplotlib 样式:
plt.style.use('classic')
在本节中,我们将按需调整此样式。请注意,此处使用的样式表从 Matplotlib 1.5 版开始得到支持;如果你使用的是早期版本的 Matplotlib ,则只能使用默认样式。样式表的更多信息,请参阅“自定义 Matplotlib:配置和样式表”。
show()
还是不show()
?如何展示你的绘图
你看不到的可视化并没什么用,但是你查看 Matplotlib 绘图的方式取决于上下文。Matplotlib 的最佳用法取决于你的使用方式;粗略地说,三个适用的上下文是,在脚本,IPython 终端或 IPython 笔记本中使用 Matplotlib。
来自脚本的绘图
如果你在脚本中使用 Matplotlib,函数plt.show()
就是你的伙伴。plt.show()
启动一个事件循环,查找所有当前活动的图形对象,并打开一个或多个显示你的图形的交互式窗口。
因此,例如,你可能有一个名为myplot.py
的文件,其中包含以下内容:
# ------- 文件:myplot.py ------
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
plt.plot(x, np.sin(x))
plt.plot(x, np.cos(x))
plt.show()
然后,你可以从命令行运行此脚本,这将打开一个窗口并显示你的图形:
$ python myplot.py
plt.show()
命令做了很多工作,因为它必须与系统的交互式图形后端进行交互。此操作的细节可能因不同系统,甚至安装而异,但 matplotlib 会尽力对你隐藏所有这些细节。
有一点需要注意:plt.show()
命令应该在每个 Python 会话中只使用一次,并且最常见于脚本的最后。多个show()
命令可能产生不可预测的后端依赖的行为,并且多数情况下应该避免。
来自 IPython shell 的绘图
在 IPython shell 中以交互方式使用 Matplotlib 非常方便(参见“IPython:不只是普通的 Python”)。如果指定 Matplotlib 模式,IPython 可以很好地配合 Matplotlib。要启用此模式,可以在启动ipython
后使用%matplotlib
魔术命令:
In [1]: %matplotlib
Using matplotlib backend: TkAgg
In [2]: import matplotlib.pyplot as plt
此时,任何plt
绘图命令都会打开一个图形窗口,可以运行更多命令来更新绘图。一些更改(例如修改已绘制的直线的属性)将不会自动绘制:要强制更新,请使用plt.draw()
。不需要在 Matplotlib 模式下使用plt.show()
。
来自 IPython 笔记本的绘图
IPython 笔记本是一个基于浏览器的,交互式数据分析工具,可以将叙述,代码,图形,HTML 元素等组合到一个可执行文档中(参见“IPython:不只是普通的 Python”)。
IPython 笔记本中的交互式绘图,可以使用%matplotlib
命令完成,其工作方式与 IPython shell 类似。在 IPython 笔记本中,你还可以选择直接在笔记本中嵌入图形,有两种可能的选择:
%matplotlib notebook
将产生嵌入在笔记本中的交互式绘图%matplotlib inline
将产生嵌入在笔记本中的绘图的静态图像
对于本书,我们通常会选择%matplotlib inline
:
%matplotlib inline
运行此命令后(每个内核/会话只需执行一次),笔记本中创建绘图的任何单元格,都将嵌入所得图形的 PNG 图像:
import numpy as np
x = np.linspace(0, 10, 100)
fig = plt.figure()
plt.plot(x, np.sin(x), '-')
plt.plot(x, np.cos(x), '--');
将图形保存到文件
Matplotlib 的一个很好的功能,是以各种格式保存图形的能力。保存图形可以使用savefig()
命令完成。例如,要将上一个图形保存为 PNG 文件,可以运行以下命令:
fig.savefig('my_figure.png')
在当前工作目录中,我们现在有一个名为my_figure.png
的文件:
!ls -lh my_figure.png
# -rw-r--r-- 1 jakevdp staff 16K Aug 11 10:59 my_figure.png
为了确认它包含我们认为它包含的内容,让我们使用 IPython Image
对象来显示该文件的内容:
from IPython.display import Image
Image('my_figure.png')
在savefig()
中,文件格式是从给定文件名的扩展名推断出来的。根据你安装的后端,可以使用许多不同的文件格式。通过使用图形画布对象的以下方法,可以找到系统支持的文件类型列表:
fig.canvas.get_supported_filetypes()
'''
{'eps': 'Encapsulated Postscript',
'jpeg': 'Joint Photographic Experts Group',
'jpg': 'Joint Photographic Experts Group',
'pdf': 'Portable Document Format',
'pgf': 'PGF code for LaTeX',
'png': 'Portable Network Graphics',
'ps': 'Postscript',
'raw': 'Raw RGBA bitmap',
'rgba': 'Raw RGBA bitmap',
'svg': 'Scalable Vector Graphics',
'svgz': 'Scalable Vector Graphics',
'tif': 'Tagged Image File Format',
'tiff': 'Tagged Image File Format'}
'''
请注意,在保存图形时,不必使用前面讨论过的plt.show()
或相关命令。
一个功能的两个接口
Matplotlib 的一个可能令人困惑的特性是它的两个接口:一个是方便的 MATLAB 风格的,基于状态的接口,以及一个更强大的面向对象的接口。我们将在这里快速强调两者之间的差异。
MATLAB 风格的接口
Matplotlib 最初编写为 MATLAB 用户的 Python 替代品,其大部分语法都反映了这一事实。MATLAB 风格的工具包含在pyplot
(plt
)接口中。例如,以下代码可能对 MATLAB 用户来说非常熟悉:
plt.figure() # 创建绘图图形
# 创建两个面板的第一个并设置当前轴
plt.subplot(2, 1, 1) # 行,列,面板号
plt.plot(x, np.sin(x))
# 创建第二个面板并设置当前轴
plt.subplot(2, 1, 2)
plt.plot(x, np.cos(x));
重要的是要注意这个接口是有状态的:它跟踪“当前”图形和轴域,这是所有plt
命令都适用的地方。你可以使用plt.gcf()
(获取当前图形)和plt.gca()
(获取当前轴域)例程来获取这些引用。
虽然这种状态接口对于简单的绘图来说既快速又方便,但很容易遇到问题。例如,一旦创建了第二个面板,我们如何返回并向第一个面板添加内容?这在 MATLAB 风格的接口中是可能的,但有点笨拙。幸运的是,有一种更好的方法。
面向对象的接口
面向对象的接口可用于这些更复杂的情况,以及需要对图形进行更多控制的时候。在面向对象的界面中,绘图函数并不依赖于“活动”图形或轴域的某些概念,而是显式“图形”和“轴域”对象的方法。要使用此风格的绘图重新创建上一个绘图,你可以执行以下操作:
# 首先创建绘图网格
# ax will be an array of two Axes objects
fig, ax = plt.subplots(2)
# 在对应对象上调用 plot() 方法
ax[0].plot(x, np.sin(x))
ax[1].plot(x, np.cos(x));
对于更简单的图,选择使用哪种样式在很大程度上取决于偏好,但随着变得更复杂,面向对象的方法可能成为必要。在本章中,我们将根据最方便的方式,在 MATLAB风 格和面向对象的界面之间切换。在大多数情况下,差异就像切换plt.plot()
到ax.plot()
一样小,但是在下面的章节中我们会强调一些问题。