matplotlib使用指南
matplotlib使用指南
声明
本文主要翻译matplotlib的官方文档,若有错误,欢迎批评指正。
本文包含了一些基本的使用方法以及适合快速上手的实例,帮助你快速熟悉matplotlib。
导入matplotlib模块的方法为
import matplotlib.pyplot as plt
import numpy as np
一个简单的例子
matplotlib将你的数据绘制在Figure上,每一个Figure能够包含一个或多个Axes。每一个Axes相当于一个绘图区域,并且该区域内的每一个点都可以通过x-y坐标,极坐标或x-y-z坐标(三维情况下)定位。
创建带有一个Axes的Figure对象的最简单方法是使用pyplot.subplots
,之后,我们便可以使用Axes.plot
将数据绘制在Axes上:
fig, ax = plt.subplots() # 创建包含一个axes的figure对象
ax.plot([1,2,3], [1,2,4]) # 将数据绘制在axes上
事实上,对于Axes中的每一个绘图函数,在matplotlib.pyplot
模块中都存在与之对应的函数。我们可以直接通过matplotlib.pyplot
自动创建Axes及其对应的Figure,并在Axes上进行绘图,并不需要显式创建Axes。因此,上述绘图的例子可以简化为:
plt.plot([1,2,3], [1,2,4])
Figure示意图
Figure
Figure负责追踪所有的子Axes,一些特殊的artists(如title,legend)和canvas(canvas是最终呈现图像的对象)。一个Figure可以包含多个Axes,但至少要包含一个Axes。
创建Figure的方法有以下几种:
fig = plt.figure() # 一个空的figure,不带有Axes
fig, ax = plt.subplots() # 带有一个Axes的figure
fig, axs = plt.subplots(2, 2) # 带有4个Axes的figure,且Axes的排序方式为2x2
在创建Figure的时候,同时创建与之对应的Axes是常用的方式。但也可以之后在创建Axes,然后加入到Figure当中,用于更复杂的Axes布局。
Axes
Axes就是我们进行绘图的区域,一个Figure可以包含多个Axes,但一个Axes只能存在于一个Figure当中。一个Axes包含两个Axis对象(三维情况下包含三个),Axis用于控制数据的显示范围。每个Axes还可以控制当前区域的标题、x-y坐标轴的名称。
fig, ax = plt.subplots()
ax.set_xlim([0, 5]) # 控制x轴显示范围
ax.set_xlabel('x') # 设置x轴名称
ax.set_ylim([-1, 3]) # 控制y轴显示范围
ax.set_ylabel('y') # 设置y轴名称
ax.set_title('ax-title')# 设置标题
Axis
Axis是类似数轴的对象,用于设置数值的显示范围、刻度标记以及刻度标签。刻度的位置由Locator
对象决定,刻度标签(字符串)由Formatter
对象生成。正确设置Locator
和Formatter
能够对刻度标记的位置和标签进行精细化的控制。
Artist
几乎所有你能够在图像上看到的对象都是一个artist(即使是Figure,Axes和Axis对象)。以及Text,Line2D,collections,Patch
对象都是artist。当图片被渲染的时候,所有的artist对象都将会被绘制到Canvas
之上。大多数的artist对象都被绑定到一个Axes之上,因此一个artist无法被多个Axes分享或移动到另一个Axes之上。
绘图函数的输入
所有的绘图函数都期望获得numpy.array
或numpy.ma.masked_array
作为输入。如果将类似数组(array-like)的对象作为输入,例如pandas.DataFrame
和numpy.matrix
,则绘图函数可能不会按照你预想的方式进行工作。因此,最好的做法是先将数据转化成numpy.array
对象。
# 将DataFrame转换成array
a = pandas.DataFrame(np.random.rand(4, 5), columns = list('abcde'))
a_asarray = a.values
# 将matrix转换成array
b = np.matrix([[1, 2], [3, 4]])
b_asarray = np.asarray(b)
面向对象接口与pyplot接口
正如在最开始的例子中所提到的,使用matplotlib由两个基本的方式:
- 面向对象风格(object-oriented style,OO style):显式创建Figure和Axes,并调用这些对象的成员函数。
- 依赖pyplot自动创建和管理Figure和Axes对象,以及使用pyplot函数进行绘图。
# 一个OO风格的示例
x = np.linspace(0, 2, 100)
# 注意:即使是OO风格, 我们仍然使用`.pyplot.figure`创建figure对象
fig, ax = plt.subplots() # Create a figure and an axes.
ax.plot(x, x, label='linear') # Plot some data on the axes.
ax.plot(x, x**2, label='quadratic') # Plot more data on the axes...
ax.plot(x, x**3, label='cubic') # ... and some more.
ax.set_xlabel('x label') # Add an x-label to the axes.
ax.set_ylabel('y label') # Add a y-label to the axes.
ax.set_title("Simple Plot") # Add a title to the axes.
ax.legend() # Add a legend.
# 一个pyplot风格的示例
x = np.linspace(0, 2, 100)
plt.plot(x, x, label='linear') # Plot some data on the (implicit) axes.
plt.plot(x, x**2, label='quadratic') # etc.
plt.plot(x, x**3, label='cubic')
plt.xlabel('x label')
plt.ylabel('y label')
plt.title("Simple Plot")
plt.legend()
事实上,存在第三种使用matplotlib的方法,但该方法的应用场景是将matplotlib集成到一个GUI应用当中。这里只是顺带提及,详细请参考Embedding Matplotlib in graphical user interfaces。
matplotlib的文档和示例使用了OO和pyplot这两种不同的风格进行演示,你可以任意选择一种你喜欢的风格书写代码。但推荐的使用方式是:在交互式终端的情况下(如jupyter notebook),使用pyplot风格;在非交互式情况下(如写python脚本的时候),使用OO风格。
后端(backends)
什么是后端?
许多网站和邮件列表的文档都提到了“后端“一词,许多新用户经常对这个术语感到困惑。matplotlib针对不同的应用场景,能够输出不同格式的结果。一些人在交互式的python shell中使用matplotlib绘图;一些人使用jupyter notebooks将matplotlib绘制的图像嵌入到代码的当中,而不是弹出单独的图片窗口;一些人将matplotlib集成到GUI程序中(如wxpython、pygtk)以创建富应用(rich application);以及其它的应用场景。
为了支持不同的应用场景,matplotlib就需要有不同的功能组件(处理机制)来针对不同场景,输出符合要求的结果。这些不同的功能组件就称为”后端“。而前端指的是面向用户的代码,即绘图的代码。后端的作用就是根据前端用户代码,生成图像,并返回给用户。matplotlib中有两种不同类型的后端:
- 交互式后端(user interface backends/interactive backends):例如pygtk,wxpython,tkinter,qt4,macosx等
- 非交互式后端(hardcopy backends/non-interactive backends):用于创建图像文件(如PNG,SVG,PDF)
如何配置后端?
一共有三种不同的方法来配置matplotlib的后端:
- matplotlibrc文件中的
rcParams['backend']
(默认是agg)参数 MPLBACKEND
环境变量matplotlib.use()
函数
需要注意的是如果同时使用了上述3种方法中的多种方法对后端进行配置,那么会优先选择序号大的方法。也就是说,如果使用函数matplotlib.use()
配置了后端,那么matplotlib将无视MPLBACKEND
和rcParams['backend']
中的参数值。
如果没有显式指定后端,matplotlib将基于系统中的可用后端和GUI事件循环(event loop)是否已经正在运行,自动检测可用的后端。在Linux系统中,如果环境变量DISPLAY
未被设置,那么事件循环将会被识别为"headless",从而导致回退到非交互式后端(agg)。
详细的配置方法如下:
-
配置matplotlibrc文件中的
rcParams['backend']
(默认是agg)参数backend : qt5agg # use pyqt5 with antigrain (agg) rendering
-
配置
MPLBACKEND
环境变量你可以在当前shell或针对单个脚本中设置该环境变量
# On Unix > export MPLBACKEND=qt5agg > python simple_plot.py > MPLBACKEND=qt5agg python simple_plot.py # On Windows,只能针对当前shell设置该环境变量 > set MPLBACKEND=qt5agg > python simple_plot.py
设置
MPLBACKEND
环境变量将覆盖掉matplotlibrc文件中的配置,即使matplotlibrc文件存在于你当前的工作目录之下。因此,将MPLBACKEND
环境变量设置为全局的环境变量(即在文件.bashrc或.profile中定义)是不妥当的,如果这样做了,可能会导致一些反直觉的结果。 -
如果你的脚本依赖于特定的后端,你可以使用
matplotlib.use()
函数import matplotlib matplotlib.use('qt5agg')
应该在Figure创建之前就调用该函数,否则matplotlib可能无法转换后端,从而导致ImportError。使用该方法指定后端,如果其它用户想要更改使用其它的后端,那么就需要对原有的代码进行改动。因此,你应该尽量避免显式调用
matplotlib.use()
指定后端,除非不得不这么做。
内置后端及其它
matplotlib的内置后端、如何使用非内置后端等信息请参考:详细信息
什么是交互(interactive)模式
使用交互式后端允许我们直接向屏幕输出图像。是否与何时向屏幕输出图像?向屏幕输出图像之后,python脚本或shell是否继续运行?这些问题都与我们所调用的函数和方法直接相关。在matplotlib中可以通过一个状态(布尔)变量的取值来控制当前是否处于“交互模式”。就像其它配置参数一样,我们可以在matplotlibrc
文件里修改状态变量。我们也可以通过matplotlib.interactive()
来设置该变量的值,以及通过matplotlib.is_isteractive()
查看该变量的值。另一种开启交互模式的方法是matplotlib.pyplot.ion()
,与之对应的关闭方法为matplotlib.pyplot.ioff()
。
注意
- 交互功能以及
show()
的角色和行为,在matplotlib更新到1.0版本后,发生了重大变化。并在1.0.1版本中修复了一些bug。这里我们将描述在1.0.1版本中基本交互式后端的行为,以及macosx中的部分例外情况。- “交互模式”能够在ipython和普通的python shell中正常运行,但不能在IDLE IDE中运行。如果默认的后端不支持交互功能,那么我们可以显式配置一个支持交互的后端,就像上述“如何配置后端?”中描述的那样。
交互模式的示例
启动一个普通的python shell或者不带任何选项参数的ipython,执行下述代码:
import matplotlib.pyplot as plt
plt.ion()
plt.plot([1.6, 2.7])
此时,将弹出一个绘图窗口。并且,终端提示符仍然处于激活状态。因此,我们可以追加新的命令,例如:
plt.title("interactive test")
plt.xlabel("index")
可以发现追加新的命令之后,绘图窗口会自动更新内容。即使使用面向对象接口更新图像,大多数的可交互后端也能够自动更新。例如:
ax = plt.gca() # 获取当前axes对象, gca = get current axes ?
ax.plt([3.1, 2.2])
如果你正在使用某些后端(例如macosx),或更老版本的matplotlib,在追加新的命令之后,绘图窗口的图形可能不会立即自动更新。在这种情况下,你需要手动调用plt.draw()
来刷新图像。
非交互模式的示例
重新启动一个新的会话窗口,并且关闭交互模式:
import matplotlib.pyplot as plt
plt.ioff()
plt.plot([1.6, 2.7])
执行完上述命令之后,不会像“交互模式”那样,立马弹出一个绘图窗口(除非你正在使用macosx后端,但这是异常情况)。为了弹出绘图窗口,我们需要手动输入:
plt.show()
弹出绘图窗口之后,你会发现此时无法像“交互模式”那样继续追加新的命令。这是因为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()
的阻塞特性,我们可以按照顺序显示一系列图像,新的图像只会在旧的图像手动关闭后才显示。
性能(performance)
无论是在交互模式中,还是在脚本中进行绘图,图像的渲染时间将会是实际任务中不得不解决的性能瓶颈。matplotlib提供了一些减少图像渲染时间的方法,代价是图像外观会发生一些改变(在我们所能容忍的范围内)。图像的类型将直接影响到我们能够使用哪些方法来减少渲染时间。
线段简化(line segment simplification)
对于包含线段的图像(例如,经典的折线图、多边形的轮廓等),可以通过变量rcParams["path.simplify"]
(默认值:True)以及rcParams["path.simplify_threshold"]
(默认值:1/9)来控制图像的渲染时间。rcParams["path.simplify"]
是个布尔变量,用于控制是否启用线段简化功能。rcParams["path.simplify_threshold"]
用于控制有多少线段会被简化,更高的阈值(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.geomspace(10, 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默认将简化阈值设置为1/9。如果你想要更改默认设置,那么可以到matplotlibrc
文件中进行设置。或者,你可能有这样的需求:在不同的应用场景中,应用不同的绘图风格。例如,在交互模式时,将线段简化阈值设置为最大值1;在有印刷图像需求时,将线段简化阈值设置为最小值0。我们可以为不同的应用场景事先配置好不同的风格表单(style sheets),等到需要时直接应用该风格表单即可。详细可参见Customizing Matplotlib with style sheets and rcParams。
线段简化的原理:通过迭代的方式,依次将线段合并成单个向量,直到下一个线段与当前向量的垂直距离大于path.simplify_threshold
。距离的计算在显示坐标空间(display-coordinate space)中进行。
注意
在2.1版本中,对线段简化如何实现进行了一些改动。对于2.1之前的版本,渲染时间仍然能通过配置这些参数提高渲染效率,但对于某些数据而言,2.1及之后的版本,渲染效率能够获得更大的改善。
标记简化(marker simplification)
标记同样能够被简化,尽管不如线段简化功能强大。标记简化只能应用于Line2D
对象,通过设置markevery
属性来实现:
plt.plot(x, y, markevery=10)
# 或者
ax.plot(x, y, markevery=10)
markevery
参数允许朴素下采样(naive subsampling),或尝试沿着x轴方向进行均匀采样,详细内容请查看Markevery Demo。
将折线划分成更小的块(splitting lines into smaller chunks)
如果你正在使用Agg后端,那么你可用通过rcParams["agg.path.chunksize"]
(默认值:0)参数指定一个块大小(chunk size)。任何顶点数大于agg.path.chunksize
的折线将会被划分成多个折线,划分后各个折线的顶点数不大于agg.path.chunksize
。如果chunksize设置为0,意味着不对原始折线进行任何划分。对于某些类型的数据,合理设置一个chunksize能够极大提高渲染效率。
下面的脚本,展示了chunkszie=0和10000的图像对比。因为在图像比较大的时候,比较容易看出二者的区别,因此请尝试最大化绘图窗口,进行观察。
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.geomspace(10, 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()
图例(legends)
图例的默认行为是在图像中找到最佳一个位置,使得被图列覆盖住的数据点最少(即loc='best'
)。当数据点非常多的时候,这种查找行为非常消耗计算资源。在这种情况下,手动指定图例的位置是更好的选择。
使用fast风格
fast风格能够自动为简化和分块参数设置一个较合理的值,依次来提高绘图效率。使用方法如下:
import matplotlib.style as mplstyle
mplstyle.use('fast')
fast风格是一种非常轻量级的配置,能够很好地与其它风格配合使用。但需要注意的是,要确保fast风格是最后一个应用生效的风格,以此保证其它风格不会覆盖掉fast风格的配置。
mplstyle.use(['dark_background', 'ggplot', 'fast'])
关于matplotlib内置风格和如何自定义风格的详细内容,参见Customizing Matplotlib with style sheets and rcParams。