Matplotlib 中文手册(1):用户使用指南

现就读于某大学计算机学院,今年大三,从高一开始入坑计算机,已逾五年,兴趣广泛,目前正在研究推荐系统与知识图谱、推荐系统与深度学习以及可解释性推荐系统。

文章正文

使用指南

本教程涉及了一些基础的图案样式用法以及最好的练习方式来帮助你开始学习 Matplotlib

基本概念

Matplotlib 具有广泛的代码库,可能会使得许多新用户望而却步。然而,绝大多数的 Matplotlib 函数可以通过相当简单的概念框架与少量的重要知识来理解。

使用 Matplotlib 绘图有一系列的操作相关,从最普遍的操作开始,例如说把一个二维数组轮廓化,到更加细化的操作,比如说我们让屏幕上的散点像素变成红色,这就是从广义到狭义,从宽泛到具体。使用 Matplotlib 绘图程序包的目的是通过有用的控制手段来帮助您尽可能地轻松完成数据的可视化。尽管我们很多时候使用的是相对高级的程序命令,但是在您有需要的时候,也可以使用较为低级的程序指令来完成您所指派的可视化工作。

因此,Matplotlib 采用层次结构来组成起代码库。顶部是它的状态机环境,由 matplotlib.pyplot 模块来提供。在这个层次上,使用简单的程序函数可以把线、图像、文本等相关绘图元素添加到当前程序所即将生成的可视化图形中。

注意:
    Pyplot 的状态机环境有点儿类似 MATLAB ,如果你曾经使用过 MATLAB ,并且具有相关经验那么学起来会非常快的。

Matplotlib 顶部的下一层次是面对对象的界面,其中 pyplot 仅仅只是用于一些功能,比如说图形创建,用户可以显式创建并且跟踪定位到图形和轴对象。在这个层次上,用户可以使用 pyplot 来创建图形,通过这些图形,用户可以创建一个或者是多个轴对象。然后,这些轴对象都能用于大多数的绘图交互中。

当然如果你们有需要的话,是可以将 pyplot 层直接删掉的,从而保存纯面向对象的方法用于进行更多地控制,比如说在 GUI 应用中嵌入由 Matplotlib 生成的图片。

图形组件

![图形解剖][1] 【此图源于 http://matplotlib.org,我对其中的英文内容做出了相关翻译标注。后续图片均由本文中的代码在线下亲自生成。】

图形

如上图,是一个完整的图形。这个图形会跟踪定位每一个子轴,以及一点点的特殊部分,比如说标题、图例等,哦,还有一个画布,实际上我们不怎么关心画布,因为你压根看不见它,它只是程序用来绘制图形用的,跟我们是没有关系的。但是要记住的是,一个图形可以有任意数量的轴线,但是至少要有一条轴,不然它就不是图形了。

pyplot 是创建一个新图形最简单的办法:

fig=plt.figure() #首先弄出一个没有轴线的空图
fig.suptitle('No axes on this figure') #添加一个新的标题,也就是做一个记号,不然后面就忘了
fig, ax_lst=plt.subplots(2,2) #这个图形是一个带有 2*2 轴的图形

这就是您所认为的“绘图”了,它是一个具有数据的图像区域。一个给定的图形可以有很多很多的轴,但是给定的轴对象只能在一个图形中出现。轴包含两个或者三个轴对象(这里要注意轴和中轴之间的差异【也就是 AxesAxis 的差异】),这些轴对象负责控制对数据的限制(数据限制可以通过 set_xlim()set_ylim()这几个轴方法来控制)每一个轴对象都有标题,可使用 set_titile() 进行设置,还有使用 set_xlabel()set_ylabel() 对 X 轴和 Y 轴进行设置。

这里再提一下,Axes 类及其成员函数是面对对象的主要接入点。

中枢轴

这个对象有点儿类似数字线。它们负责设置图形限制并且生成刻度以及刻度标签。刻度位置由 Lacator 对象确定,刻度标签字符串由 Formatter 格式化。刻度定位器和字符格式化程序之间的正确组合可以很好的控制刻度线的位置及其标签内容。

美术内容

当你使用 Matplotlib 进行绘图时,你就已经是一个可爱的艺术家了。基本上,你在图形上看见的所有内容都是美术作品,甚至包括图、轴和中枢轴对象。比较普通的就是比如说,Text 对象, Line2D 对象,collection 对象,Patch 对象等。在绘制完图形之后,所有的美术内容都会被绘制在画布上。绝大多数的美术内容是与轴捆绑在一起的,这样的美术内容没办法多轴共享,也不能轴对轴移动。

绘图功能的输入类型

所有的绘图功能,都希望是以 np.array 或者 np.ma.masked_array 作为输入的。“类似数组”的类,比如说 pandas 数据对象或者 np.matrix 可能就无法正常工作了。所以在绘制图形之前,请务必先把它们转换成 np.array 对象。

举个例子,转换 pandas.DataFrame

a = pandas.DataFrame(np.random.rand(4,5), columns = list('abcde'))
a_asarray = a.values

再举个例子,转换 np.matrix

b = np.matrix([[1,2],[3,4]])
b_asarray = np.asarray(b)

Matplotlib, pyplot 和 pylab 之间有什么关系?

Matplotlib 是一个完整的软件包,而 matplotlib.pyplotMatplotlib 中的一个模块。

对于 pyplot 模块中的功能,始终有一个“当前”图形和轴(根据要求自动创建)。 例如,在下面的示例中,对 plt.plot 的第一次调用将创建轴,然后对 plt.plot 的后续调用将在同一轴上添加其他行,并且使用 plt.xlabelplt.ylabelplt.titleplt.legend 设置轴标签、标题和图例。

x = np.linspace(0, 2, 100)

plt.plot(x, x, label='linear')
plt.plot(x, x**2, label='quadratic')
plt.plot(x, x**3, label='cubic')

plt.xlabel('x label')
plt.ylabel('y label')

plt.title("Simple Plot")

plt.legend()

plt.show()

<center>![基本图示 1][2]</center>

pylab 是一个便捷模块,可在单个命名空间中批量导入 matplotlib.pyplot(用于绘图)和 numpy(用于数学以及使用数组)。 pylab 已过时,并且由于命名空间的污染而强烈不建议使用 pylab,请使用 pyplot 代替。

对于非交互式绘图,建议使用 pyplot 创建图形,然后使用面对对象接口进行绘图。

编程风格

查看本文档和示例时,您会发现不同的编码样式和使用模式。这些样式完全有效,各有利弊。几乎所有示例都可以转换为另一种样式并获得相同的结果。唯一的忠告,是请避免将您自己的代码的编码样式混合在一起。

注意:
    matplotlib 的开发人员必须遵循特定的样式和准则。 请参阅《 Matplotlib 开发人员指南》。

在不同的代码风格中,有两种得到官方支持。因此,这两种是使用 Matplotlib 的首选风格。

对于 pyplot 样式,导入的方式通常为:

import matplotlib。pyplot as plt
import numpy as np

然后调用其他函数时,比如说 np.arangenp.zerosnp.piplt.figureplt.plotplt.show 等。可以使用 pyplot 接口先创建图形,然后使用对象方法进行其他相关操作:

x = np.arange(0, 10, 0.2)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y)
plt.show()

<center>![基本图示 2][3]</center>

那么,为什么要使用所有其他类型而不是 MATLAB 样式(依赖于全局状态和平面名称空间)呢?对于像此示例这样的非常简单的事物,唯一的优势是学术性:字词风格更加明确,对于事物的来源和发生的情况更加清楚。对于更复杂的应用程序,这种明确性和清晰度变得越来越有价值,并且更丰富,更完整的面向对象的界面将可能使程序更易于编写和维护。

通常,人们会发现自己一遍又一遍地绘制相同的图,但是使用不同的数据集,这导致需要编写专门的函数来进行绘制。 推荐的函数名称类似于:

def my_plotter(ax, data1, data2, param_dict):
    out = ax.plot(data1, data2, **param_dict)
    return out

data1, data2, data3, data4 = np.random.randn(4, 100)
fig, ax = plt.subplots(1, 1)
my_plotter(ax, data1, data2, {'marker': 'x'})
#如果你希望输出图像的话,加上下面这句话
plt.show()

<center>![示例图像 3][4]</center>

当然,如果你没有密集恐惧症的话,我推荐你试试两个子图:

fig, (ax1, ax2) = plt.subplots(1, 2)
my_plotter(ax1, data1, data2, {'marker': 'x'})
my_plotter(ax2, data3, data4, {'marker': 'o'})

<center>![示例图像 4][5]</center>

那就先介绍到这里,后面还有关于后端的部分,我认为目前没有必要说了。

后端

什么是后端

在官网和相关邮件列表上的许多文档都有提到“后端”,许多新用户对这个词很困惑。有些人从 python shell 交互使用 matplotlib,并且在键入命令后弹出绘图窗口;有些人运行 Jupyter notebooks 并绘制内联图以进行快速数据分析;有些人则将 matplotlib 嵌入到 wxpython 或 pygtk 等图形用户界面中,以构建丰富的应用程序;有些人在批处理脚本中使用 matplotlib 从数值模拟中生成可视化图形;还有一些人运行 Web 服务以动态地提供图形。

为了支持上述的所有相关用例或相关操作,Matplotlib 针对不同的输出,给定了能支持这些操作的功能,我们统称为“后端”。“前端”是用户面对的代码,也就是绘图代码,比如说上面我们讲的那些的,而“后端”则是计算机在幕后进行的一些绘图工作。“后端”有两种类型:

  • 交互式后端:用于 pygtk,wxpython,tkiner,qt4,macosx。
  • 非交互式后端:主要适用于制作图像文件的硬拷贝后端,比如说 PNG,SVG,PDF 还有 PS 文件。

Matplotlib 中有四种配置后端的方法。如果它们之间互相冲突,那么我们可以调用 use() 覆盖 matplotlibrc 中的设置。

注意:
    后端名称规范不区分大小写。"A" 和 "a" 是等效的。

什么是交互模式

使用交互式后端允许程序绘图到屏幕上,还有何时在屏幕上进行绘制,以及绘图结束之后程序会话是否还要继续,这些都取决于调用的函数以及 matplotlib 是否处于“交互模式”的状态下。默认状态的控制变量是由 matplotlibrc 文件设置的,可以进行任意的自定义调整,也可以通过 matplotlib.interactive() 进行设置,还能够使用 matplotlib.is_interactive() 查询相关变量的值。我们很少在程序命令的中间突然打开或者关闭“交互模式”,因此在下面的所有案例中,我们都假定“交互模式”是从一开始就默认打开或者默认关闭的,就是不会在中途突然关掉或者突然打开。

注意:
    在向 matplotlib 版本 1.0 过渡时,我们对交互性相关方面做出了重大更改,尤其是 show()的角色和行为。在这里,我们将主要描述版本 1.0.1 的交互式后端操作,但 macosx 的部分例外。

顺带一提,交互模式使用 matplotlib.pyplot.ion() 打开,使用 matplotlib.pyplot.ioff() 关闭。

注意:
    交互式后端可以在 ipython 或者 python shell 中与合适的后端操作一起使用,但是在 idle 中不起作用。如果默认的后端不支持交互性,可以考虑显示激活交互式后端。

交互案例

在一般的 python 提示符下,或者在不带任何选项的情况下调用 ipython 之后,请尝试以下操作:

import matplotlib.pyplot as plt
plt.ion()
plt.plot([1.6, 2.7])

<center>![示例图像 5][6]</center>

假设您当前正在运行的是 1.0.1 或更高版本,并且在默认情况下已安装并选择了一个交互式后端,则应该会看到上面这个图,并且终端提示也应处于活动状态。您可以键入其他命令,例如:

plt.title("interactive test")
plt.xlabel("index")

<center>![示例图像 6][7]</center>

您会在每行之后看到最早的图像正在更新。从 1.5 版开始,通过其他方式修改绘图也会自动更新。获取对 Axes 实例的引用,并调用该实例的方法如下:

ax = plt.gca()
ax.plot([3.1, 2.2])

<center>![示例图像 7][8]</center>

如果您使用某些后端(例如 macosx)或者较旧的 matplotlib 版本,则可能不会立即看到新线添加到刚刚所绘制的图像中。在这种情况下,您需要显式调用 draw() 来更新绘图:

plt.draw()

非交互模式

与前面的示例一样,开始一个新的程序,但是现在关闭交互模式:

import matplotlib.pyplot as plt
plt.ioff()
plt.plot([1.6, 2.7])

啥都不会出现,如果想让它出现的话,你得加上一句话:

plt.show()

现在您看到了该图,但是您的终端命令行没有其他响应。这是因为 show() 命令将阻止其他命令的输入,直到您手动终止绘图窗口为止。

非交互模式会延迟所有绘图,直到调用 show() 为止。这会比每次往脚本中添加新的一行功能时重新绘图来的更合理。

在版本 1.0 之前,show() 通常不能在一个脚本中被多次调用,然而对于 1.0.1 以后的版本,这个限制就被接触了,因此可以编辑以下脚本:

import numpy as np
import matplotlib.pyplot as plt

plt.ioff()
for i in range(3):
    plt.plot(np.random.rand(10))
    plt.show()

上面这段代码所表示的含义是,绘制三次图像,当然第一次图像显示完成之后,随即进行下一次,然后用下一次的图像覆盖第一次的图像。

总结

在交互模式下,pyplot 函数会自动绘制图形到屏幕上。

交互式绘图时,如果除了使用 pyplot 函数外还使用对象方法调用,则每当您要刷新绘图时都得调用 draw()

在非交互模式下,使用 show() 显示图形并阻止新的脚本代码执行,直到您手动销毁之前生成的图形为止。

性能

无论是以交互方式浏览数据还是以编程方式保存大量绘图,渲染性能都可能成为一个痛苦的瓶颈。Matplotlib 提供了几种方法,以稍微改变绘制外观为代价,大大减少了渲染时间。减少渲染时间的可用方法取决于所创建图的类型。

线段简化

对于具有线段的图(例如典型的线图,多边形的轮廓等),可以通过 matplotlibrc 文件中的 path.simplifypath.simplify_threshold 参数来控制渲染性能。path.simplify 参数是一个布尔值,指示是否完全简化线段。path.simplify_threshold 参数控制简化的线段数量; 阈值越高,渲染越快。

以下代码将首先显示数据而不进行任何简化,然后以简化的方式显示相同的数据:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

# Setup, and create the data to plot
y = np.random.rand(100000)
y[50000:] *= 2
y[np.logspace(1, np.log10(50000), 400).astype(int)] = -1
mpl.rcParams['path.simplify'] = True

mpl.rcParams['path.simplify_threshold'] = 0.0
plt.plot(y)
plt.show()

mpl.rcParams['path.simplify_threshold'] = 1.0
plt.plot(y)
plt.show()

能够切实地体验到两次图像的出现速度是不一致的,第一次没有简化的图像出现的非常慢,第二次有进行简化的,速度很快。两个图形在视觉感官上的体验是很接近的。

Matplotlib 当前默认的简化阈值是 $\frac{1}{9}$,如果要更改默认设置以使用其他值,可以更改 matplotlibrc文件进行相应设置。另外,您可以创建一种新样式(用于最大程度简化)交互式绘图。有关如何执行这些操作的说明,请参见使用样式表和 rcParams 用以自定义 Matplotlib 。

简化工作是将线段迭代合并到单个矢量中,直到下一个线段到矢量的垂直距离(在坐标空间中测量)大于 path.simplify_threshold 参数。

注意
    在版本 2.1 中进行了有关简化线段的更改。在 2.1 之前的版本中,这些参数仍会改善渲染时间,但是在 2.1 及更高版本中,某些类型数据的渲染时间将得到比之前更大的改善。

标记简化

标记也可以简化,尽管不如线段那样子具有健壮性。简化标记仅适用于 Line2D 对象(通过 markevery 属性)。在传递 Line2D 构造参数的任何位置(例如 matplotlib.pyplot.plot()matplotlib.axes.Axes.plot()),都可以使用 markevery 参数:

plt.plot(x, y, markevery=10)

markevery 参数允许进行简单的二次采样,或尝试均匀间隔采样。有关更多信息,请参见 Markevery 演示。

行分块

如果使用的是 Agg 后端,则可以使用 agg.path.chunksize 的 rc 参数指定块的大小,任何大于多个顶点的线都将被拆分为多行,每行最多包含 agg.path.chunksize 个顶点。除非 agg.path.chunksize 为零,否则不进行分块。对于某些类型的数据,将行分块为合理的大小可以大大减少渲染时间。

以下代码将首先显示没有任何块大小限制的数据,然后显示块大小为 10,000 的相同数据。当数字很大时,能看见很大的区别:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
mpl.rcParams['path.simplify_threshold'] = 1.0

# Setup, and create the data to plot
y = np.random.rand(100000)
y[50000:] *= 2
y[np.logspace(1,np.log10(50000), 400).astype(int)] = -1
mpl.rcParams['path.simplify'] = True

mpl.rcParams['agg.path.chunksize'] = 0
plt.plot(y)
plt.show()

mpl.rcParams['agg.path.chunksize'] = 10000
plt.plot(y)
plt.show()

图例

默认图例会尝试查找覆盖最少数据点的位置 loc='best' 。如果有很多数据点,这可能要消耗很多算力。在这种情况下,您可能需要提供一个特定的位置以方便图形渲染。

使用快捷样式

快速样式可将简化和分块参数自动设置为合理的设定,以加快绘制大量数据的速度。只需运行以下命令即可使用它:

import matplotlib.style as mplstyle
mplstyle.use('fast')

它的代码量很小,因此可以与其他样式很好地配合,只要确保最后应用快速样式即可,这样其他样式就不会覆盖设置:

mplstyle.use(['dark_background', 'ggplot', 'fast'])

作为系列性翻译,我已开始着手进行下一部分 Pyplot tutorial 的相关翻译。点击此处可以跳转到 Usage Guide 的原版文献。

作者正在撰写中...
× 订阅 Java 精选频道
¥ 元/月
订阅即可免费阅读所有精选内容