本指南主要包含一些基本的使用模式和最基本方法来使用matplotlib。
import matplotlib.pyplot as plt
import numpy as np
基本都以这两行代码作为开始代码,这样导入matplotlib库和numpy数据处理库。
Matplotlib简单的例子
matplotlib可以画布上可视化你的数据,而画布可以是Windows窗口、Jupyter窗口等等,每个画布包含有一个或多个坐标轴,这里所指的坐标轴可以是X-Y的直角坐标轴,也可以是角度-半径的极坐标轴,还可以是X-Y-Z的3D坐标轴。最简单地创建一个画布并带有坐标轴的方式是使用pyplot.subplots函数,然后使用它的轴Axes.plot函数来绘图:
fig, ax = plt.subplots() # 创建一个带坐标轴的画布.
ax.plot([1, 2, 3, 4], [1, 4, 2, 3]) # 在坐标轴里显示一些数据.
许多数据可视化的库或者语言,常常不需要明确地创建坐标轴,就可以直接地绘图,比如MATLAB,它能如下操作:
plot([1, 2, 3, 4], [1, 4, 2, 3]) % MATLAB plot.
就立即得到绘图结果。
其实在matplotlib里,也可以使用同样的方式,每个Axes绘图的方法,都可以直接在matplotlib.pyplot里以函数的方式使用,因为它绘图都是在当前坐标轴里显示,如果当前坐标轴不存在,它会立即创建一个坐标轴。因此前面的代码可以简单地写成这样:
import numpy as np
from matplotlib import pyplot as plt
plt.plot([1, 2, 3, 4], [1, 4, 2, 3]) # Matplotlib 画曲线.
plt.show()
Matplotlib画布的基本组成
画布
整个窗口是一个画布,画布包含坐标轴、一些特殊的组成部分(标题、图例等等)、绘图区。画布可以包含有多个坐标轴,但至少要包含一个。
最简单地创建一个新画布的方法是使用pyplot函数:
fig = plt.figure() # 一个空画布没有创建坐标轴
fig, ax = plt.subplots() # 一个带有坐标轴的画布
fig, axs = plt.subplots(2, 2) # 一个带有 2x2 网络布局的4个坐标轴的画布
与画布一起创建的坐标轴是很方便,不过你也可以先创建画布,再添加坐标轴,这样允许你创建复杂的坐标轴,因为与画布一起创建的坐标轴,都是简单的、常用的,或许不能满足你的需求。
坐标系
坐标系,就是你认为的“绘图”的区域,它是图像的区域和数据空间。给定的图形可以包含多个坐标系,但给定的坐标系对象只能位于一个图形中。在直角坐标系中,常常是包含两个坐标轴对象Axis,但在3D的坐标系里包含有三个坐标轴对象。可以通过坐标轴来控制数据显示的范围,比如通过 axes.Axes.set_xlim() 和 axes.Axes.set_ylim()方法。每个坐标系有三个标题,坐标系标题(set_title())、X轴标题(set_xlabel())和Y轴标题(set_ylabel())。
坐标系的类和函数,它们是整个面向对象绘图的入口点。
坐标轴
有许多数字标记显示的直线类似对象。坐标轴的功能主要有限制图形显示范围,生成坐标轴的刻度和刻度标识。刻度怎么样分配是一个重要的功能,它可以通过Locator对象来设置刻度分配的方式,比如是1、2、3、4、5、6的分配,还是1、5、10的分配。刻度的标识,可以通过类Formatter来管理,比如实现显示数字的方式,显示2021年还是21年的样子。通过Locator和Formatter就可以很方便地控制坐标轴的刻度布局和刻度标记的字符串显示方式。
Artist(绘图组件)
基本上在画布里看到的对象,都是绘图组件,甚至画布、坐标系、坐标轴对象。绘图组件包括Text对象、Line2D对象、collections对象, Patch对象…。当画布进行显示时,所有绘图对象都会被显示到画布上面,大多数绘图对象是绑定到坐标系,因此绘图组件不能在多个坐标系里共享,或者从一个坐标系移动到另一个坐标系。
绘图函数的输入类型
在matplotlib里大部分函数都是接受 numpy.array 或 numpy.ma.masked_array数据类型输入,如果是其它类型的数据格式需要转换为这两种类型才能绘图。
比如pandas的数组或者numpy.matrix类型,都需要在绘图之前进行转换。
例如把pandas.DataFrame类型进行转换:
a = pandas.DataFrame(np.random.rand(4, 5), columns = list('abcde'))
a_asarray = a.values
或者 numpy.matrix转换:
b = np.matrix([[1, 2], [3, 4]])
b_asarray = np.asarray(b)
面向对象的接口和pyplot接口
基本上存在两种接口方式来使用matplotlib:
1)明确地创建画布和坐标系,然后调用这些对象的方法,这种方式叫做面向对象的方式。
2)依赖pyplot自动地创建画布和坐标系,然后调用pyplot里的函数来绘图,这种方式pyplot函数接口方式。
面向对象的方式,如下例子:
x = np.linspace(0, 2, 100)
# 注意,即使使用面向对象的方式,也可以使用`.pyplot.figure`创建一个画布。
fig, ax = plt.subplots() # 创建一个画布和坐标系.
ax.plot(x, x, label='linear') # 在坐标系里显示一些数据
ax.plot(x, x**2, label='quadratic') # 在坐标系里显示更多数据
ax.plot(x, x**3, label='cubic') # 在坐标系里显示更多数据
ax.set_xlabel('x label') # 添加x-标题到坐标系。
ax.set_ylabel('y label') # 添加y-标题到坐标系。
ax.set_title("Simple Plot") # 添加标题到坐标系。
ax.legend() # 添加一个图例显示.
使用pyplot的方式:
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()
实际上还有第三种显示方式,当你添加matplotlib到一个GUI的应用程序时,可以直接进行绘图。我们就不再讨论这种方式,如果有兴趣的读者,可以参考:https://matplotlib.org/gallery/index.html#user-interfaces
matplotlib的文档和例子使用了面向对象的接口和pylot的接口,它们是等同的功能,你可以自由地使用它们,不过你只要选择其中一种使用,不要混合地使用它们。也就是说,不要既使用面向对象的接口,又同时使用pylot的接口。
一般来说,我们建议在交互的环境里,比如Jupyter,使用pyplot接口方式,而在非交互的环境里使用面向对象接口,比如写一些函数和脚本,这样更方便代码的复用和应用到大工程项目里。
注意:
在比较旧的例子里,你也许发现一些例子使用pylab接口,比如 from pylab import *,这是一个智能接口,自动地导入了pyplot和numpy的库,可以接使用:
x = linspace(0, 2, 100)
plot(x, x, label='linear')
...
针对这种代码,主要是模拟MATLIB类似的代码,目前基本已经放弃这种编写方式,建议你不要再学习这种方式,这里只是提及一下,偶尔你碰到旧代码或许看到这种代码。
通常情况下,人们会发现自己一次又一次地绘制相同的图,但是使用不同的数据集,这导致需要编写专门的函数来进行绘制。因此写一些共享的函数,比如:
def my_plotter(ax, data1, data2, param_dict):
"""
帮助绘图的公共函数
参数----------
ax : 坐标系 将要绘图的坐标系
data1 : 数组 X轴的数据
data2 : 数组 Y轴的数据
param_dict : 字典
kwargs参数,传送给ax.plot函数
返回值-------
out : 列表 绘图组返回
"""
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'}
或者你需要在两个坐标系里绘制:
fig, (ax1, ax2) = plt.subplots(1, 2)
my_plotter(ax1, data1, data2, {'marker': 'x'})
my_plotter(ax2, data3, data4, {'marker': 'o'})
对于这些简单的例子来说,这种风格要求似乎有点过分,但是一旦图形变得稍微复杂一些,它就会得到回报。
后端(Backends)
后端是什么?
在网络上有很多文档和邮件列表引用后端这个词,也有很多新用户对于后端这个概念感到迷惑。matplotlib针对许多不同的环境和输出格式的解决方案,有些人使用matplotlib在交互式的环境里,有些人通过命令窗口里使用绘图,有些人使用于Jupyter里绘图和分析数据。
有些人嵌入到图形用户界面的应用程序里,比如wxpython,或者pygtk。有些人用于批处理的脚本里生成模拟数据的图片,有些人用于WEB网站里动态在生成可视化图表。
为了支持各种的使用环境,matplotlib可以配置不同的输出方式,每一种输出方式就叫做后端。相对而言,前端就是用户面对的代码,绘图的代码。因此后端就是在每种场景之下进行各种绘图的工作。通常有两种后端:一种是用户界面类似的后面(pygtk, wxpython, tkinter, qt4, 或 macosx,这是交互式的后端),一种是硬拷贝的图像后端(PNG, SVG, PDF, PS;也叫做非交互式的后端)。
怎么样选择后端
matplotlib提供了三种后端的配置方式:
1) 设置rcParams[“backend”]参数,默认值是agg,在matplotlibrc文件里。
2)设置MPLBACKEND环境变量。
3)使用matplotlib.use()
现在更详细地说明一下,当你同时配置了这三种方式,最后面那种方式优先级就最高,比如调用matplotlib.use()之后就会替换之前设置matplotlibrc参数。
如果没有明确的后端设置,matplotlib会自动根据你的系统和依赖的GUI事件循环来选择一个合适的后端。在Linux系统下,如果环境变量 DISPLAY并没有设置,并且事件循环设置为headless,那么就会导致退回到设置非交互的后端(agg)。
下面是后端的详细配置方法:
1)在matplotlibrc文件里设置rcParams[“backend”],默认是agg。
backend : qt5agg # use pyqt5 with antigrain (agg) rendering
具体可以查看:https://matplotlib.org/tutorials/introductory/customizing.html
2)设置 MPLBACKEND环境变量
可以当前的shell或单一的脚本里设置这个环境变量。
在Unix可以这样配置:
> export MPLBACKEND=qt5agg
> python simple_plot.py
> MPLBACKEND=qt5agg python simple_plot.py
在Windows系统里,可以这样配置:
> set MPLBACKEND=qt5agg
> python simple_plot.py
当你设置环境变量时,它会覆盖在matplotlibrc里配置的后端参数,即使你把matplotlibrc放在当前参数的目录下面。因此设置环境变量的方式,是全局化的,比如在.bashrc 或 .profile文件里。
3)在脚本使用matplotlib.use()函数来选择后端。
import matplotlib
matplotlib.use('qt5agg')
这两行要放在任何创建画布之前调用,否则matplotlib会切换后端失败,并且抛出ImportError异常。
当用户选择不同的后端时才在代码使用这种方式。因此,除非绝对必要,否则应避免显式调用matplotlib.use。
内置的后端方式
默认的情况下,matplotlib自动地选择一种合适的后端,它不仅可以在交互环境下工作正常,也可以在脚本里正常使用。不仅可以输出到屏幕,也可以输出到文件,至少不用担心后端会有什么不正常 。极端的情况下常见的问题是你的python安装的环境没有带有tkinter,并且没有其它的GUI库安装,这常常出现在某些Linux发行版本里,此时你需要安装python-tk安装包,或者类似的安装包。如果你想使用在交互式的用户界面里,或者WEB应用程序里,或者想更好地理解后端的工作原理,请继续看下去。
为了使图形用户界面更具可定制性,matplotlib将渲染器(实际绘制图形的对象)的概念与画布(图形所在的位置)分开。经典的渲染器是Agg,它是Anti-Grain Geometry的C++库,用来创建一个光栅图像。经常使用的有Qt5Agg, Qt4Agg, GTK3Agg, wxAgg, TkAgg, 和 macosx 后端。另一种渲染器基于Cairo库,Qt5Cairo, Qt4Cairo等等。
对于渲染引擎,还可以区分向量渲染器和光栅渲染器。向量渲染器像绘图语言一样,比如从一点到另外一点画一条直线,并且可以自由地缩放,反之光栅渲染器是用像素来表示直线绘制,依赖于DPI的精度设置。
下面常用的渲染器:
在非交互的环境下,要想保存一个绘图的图片,可以使用matplotlib.pyplot.savefig(‘filename’)方法。
下面是一些交互式和文件保存式的渲染器后端:
注意:内置后端的名称是大小写敏感的,比如QT5Agg与qt5agg是两个不同的后端。
ipympl
由于Jupyter部件更新得太快,所以无法在matplotlib里直接地支持,需要安装ipympl组件:
pip install ipympl
jupyter nbextension enable –py –sys-prefix ipympl
或者:
conda install ipympl -c conda-forge
具体可以查看网站:https://github.com/matplotlib/ipympl
使用非内置的后端
一般来说,任何可导入的后端都可以使用上述任何方法进行选择。假如name.of.the.backend包含了一个后端,那么就可以使用module://name.of.the.backend,比如matplotlib.use(‘module://name.of.the.backend’)方式。
什么是交互方式
使用交互式后端(请参阅什么是后端?)允许——但本身并不要求或确保——在屏幕上显示出来。是否以及何时打印到屏幕,以及在屏幕上绘制打印后脚本或shell会话是否继续,取决于调用的函数和方法,以及确定matplotlib是否处于“交互模式”的状态变量。在参数matplotlibrc 文件的默认配置,或者调用 matplotlib.interactive()函数设置,或者 matplotlib.is_interactive()设置,都会影响交互模式。交互模式的打开或关闭,无论是在脚本中还是在shell中,在打印命令流的中间打开和关闭交互模式是很少需要的,而且可能会造成混淆,因此在下面我们将假设所有打印都是在打开或关闭交互模式的情况下完成的。
可以通过 matplotlib.pyplot.ion()函数来打开交互械,或者通过 matplotlib.pyplot.ioff()来关闭交互模式。不过要注意,交互模式适用于ipython和python的shell模式,但是不适合IDLE IDE。
交互模式的例子
在一个交互的python提示符或者ipython后面,尝试输入下面的代码:
import matplotlib.pyplot as plt
plt.ion()
plt.plot([1.6, 2.7])
这将会弹出一个绘图窗口,但是提示符的窗口仍然保持在激活状态,所以可以继续输入下面的命令:
plt.title("interactive test")
plt.xlabel("index")
大多数交互式的后端,画布窗口可以通过面向对象的接口来更新,比如引用一个坐标系对象的实例,然后调实例的方法:
ax = plt.gca()
ax.plot([3.1, 2.2])
如果你使用某种特别的后端(比如macosx)或者旧版本matplotlib,你也许看不到修改代码后立即更新绘图,在这种情况下,你需要调用下面函数:
plt.draw()
非交互模式的例子
仍然使用前面的例子,只不过把交互的模式关闭:
import matplotlib.pyplot as plt
plt.ioff()
plt.plot([1.6, 2.7])
这时候你应该看不到任何绘图窗口,为了显示绘图结果,需要明确调用函数:
plt.show()
现在你才能看到绘图结果,并且你在终端里交互的命令行是非激活状态,pyplot.show()函数已经阻塞了命令输入,直到你关闭绘图窗口。
这样做有什么好处——被迫使用阻塞函数?假设您需要一个脚本将文件的内容打印到屏幕上。你想看看情节,然后才结束剧本。如果没有诸如show()之类的阻塞命令,脚本将刷新绘图,然后立即结束,在屏幕上什么也不留下,这样你什么也看不到。
另外,非交互方式的模式下,所有绘图都是在调用show()之后才会调用,这样就更加有效地绘制,比交互的模式效率更高。
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提供了两种方法,可以极大地减少渲染时间,但代价是对绘图的外观进行细微的更改(达到可设置的公差)。减少渲染时间的可用方法取决于正在创建的显示类型。
线段简化
可以通过下面两个参数来配置:
rcParams[“path.simplify”] (default: True) 和 rcParams[“path.simplify_threshold”] (default: 0.111111111111)
rcParams[“path.simplify”] (default: True)是一个布尔常量,指出线段是否进行简化绘制。
rcParams[“path.simplify_threshold”] (default: 0.111111111111)是控制了线段简化绘制,当阈值设置更高就会渲染更快。
下面的例子来演示没有线段简化和进行线段简化的演示:
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()
标记简化
标记简化也是可以进行性能优化,尽管它没有绘图数据优化那么明显。标记简单仅适用于Line2D的对象,无论Line2D构造参数通过何处设置,例如matplotlib.pyplot.plot()和matplotlib.axes.axes.plot(),可以使用markevery参数:
plt.plot(x, y, markevery=10)
markevery参数允许简单的子采样,或尝试均匀间隔(沿x轴)采样。
直线分割成小段进行优化
如果你使用Agg后端,那么通过设置 rcParams[“agg.path.chunksize”] (default: 0) 参数来优化性能,比如下面的例子:
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()
图例优化
在图例显示时,它总是寻找一个数据点最少显示的地方来显示图例,默认的参数是loc=’best’,这将导致在大量数据显示时需要计算量非常大。
在大数据的情况下,需要明确地指定一个位置来显示,不能让matplotlib来自动寻找位置显示。
使用快速显示类型
快速显示类型可以自动设置简化线段和分段参数,用来加速大量数据显示的情况:
import matplotlib.style as mplstyle
mplstyle.use('fast')
它是非常轻的重量,所以它可以很好地发挥与其他风格,只要确保快速风格是最后应用,使其他风格不会覆盖设置。
mplstyle.use(['dark_background', 'ggplot', 'fast'])