Python 数据分析:使用 matplotlib 快速绘图
matplotlib 是 Python 中最流行的数据可视化库之一,它采用面向对象的技术来实现。因此组成图表的各个元素都是对象,在编写较大的应用程序时通过面向对象的方式使用 matplotlib 将更加有效。但是使用这种面向对象的调用接口进行绘图比较烦琐,因此 matplotlib 还提供了快速绘图的 pyplot 模块。本节首先介绍该模块的使用方法。
本文介绍了 Python 中 matplotlib 库的快速绘图方法。涵盖 pyplot 模块的基本调用、面向对象绘图方式、属性配置、多子图绘制技巧、配置文件修改以及中文显示解决方案。通过实例演示了如何创建图表、设置样式、保存图像及处理复杂布局,帮助开发者高效完成数据可视化任务。文章还补充了常见问题与最佳实践,包括图片清晰度优化、中文乱码处理及大数据量下的性能提升策略。

matplotlib 是 Python 中最流行的数据可视化库之一,它采用面向对象的技术来实现。因此组成图表的各个元素都是对象,在编写较大的应用程序时通过面向对象的方式使用 matplotlib 将更加有效。但是使用这种面向对象的调用接口进行绘图比较烦琐,因此 matplotlib 还提供了快速绘图的 pyplot 模块。本节首先介绍该模块的使用方法。
为了将 matplotlib 绘制的图表嵌入 Jupyter Notebook 中,需要执行下面的命令开启内联模式:
%matplotlib inline
使用 inline 模式在 Notebook 中绘制的图表会自动关闭,为了在 Notebook 的多个单元格内操作同一幅图表,需要运行下面的魔法命令来保持图表状态:
%config InlineBackend.close_figures = False
此外,为了优化渲染性能,还可以设置 DPI(每英寸点数),默认通常为 100,可以根据需求调整:
plt.rcParams['figure.dpi'] = 300
matplotlib 的 pyplot 模块提供了与 MATLAB 类似的绘图函数调用接口,方便用户快速绘制二维图表。我们先看一个简单的例子:
import matplotlib.pyplot as plt
import numpy as np
# 生成数据
x = np.linspace(0, 10, 1000)
y = np.sin(x)
z = np.cos(x**2)
# 创建画布
plt.figure(figsize=(8, 4))
# 绘制曲线
plt.plot(x, y, label="$sin(x)$", color="red", linewidth=2)
plt.plot(x, z, "b--", label="$cos(x^2)$")
# 设置坐标轴标签和标题
plt.xlabel("Time(s)")
plt.ylabel("Volt")
plt.title("PyPlot First Example")
plt.ylim(-1.2, 1.2)
# 显示图例
plt.legend()
# 显示图表
plt.show()
程序输出为一条正弦波和一条余弦平方波。以下是关键步骤的详细解析:
plt 以简化后续调用。figure() 创建一个 Figure(图表)对象,并且它将成为当前 Figure 对象。figsize 参数指定 Figure 对象的宽度和高度,单位为英寸。此外还可以用 dpi 参数指定 Figure 对象的分辨率,即每英寸所表示的像素数。例如本例中所创建的 Figure 对象的宽度为 8*80 = 640 个像素。plot() 在当前的 Figure 对象中绘图。实际上 plot() 是在 Axes(子图)对象上绘图,如果当前的 Figure 对象中没有 Axes 对象,将会为之创建一个几乎充满整个图表的 Axes 对象,并且使此 Axes 对象成为当前的 Axes 对象。plot() 的前两个参数是分别表示 X、Y 轴数据的对象,这里使用的是 NumPy 数组。使用关键字参数可以指定所绘制曲线的各种属性:
label:给曲线指定一个标签,此标签将在图示中显示。如果标签字符串的前后有字符 $,matplotlib 会使用内嵌的 LaTeX 引擎将其显示为数学公式。color:指定曲线的颜色,颜色可以用英文单词或以 # 字符开头的 6 位十六进制数表示,例如 #ff0000 表示红色。或者使用值在 0 到 1 范围之内的三个元素的元组来表示,例如 (1.0, 0.0, 0.0) 也表示红色。linewidth:指定曲线的宽度,可以不是整数,也可以使用缩写形式的参数名 lw。'b--' 指定曲线的颜色和线型,它通过一些易记的符号指定曲线的样式。其中 'b' 表示蓝色,'--' 表示线型为虚线。在 IPython 中输入 plt.plot? 可以查看格式化字符串以及各个参数的详细说明。xlabel、ylabel:分别设置 X、Y 轴的标题文字。title:设置子图的标题。xlim、ylim:分别设置 X、Y 轴的显示范围。legend:显示图示,即图中表示每条曲线的标签 (label) 和样式的矩形区域。plt.show() 显示绘图窗口,在 Notebook 中可以省略此步骤。在通常的运行情况下,show() 将会阻塞程序的运行,直到用户关闭绘图窗口。还可以调用 plt.savefig() 将当前的 Figure 对象保存成图像文件,图像格式由图像文件的扩展名决定。下面的程序将当前的图表保存为 test.png,并且通过 dpi 参数指定图像的分辨率为 120,因此输出图像的宽度为 8*120 = 960 个像素。
plt.savefig("test.png", dpi=120)
如果关闭了图表窗口,则无法使用 savefig() 保存图像。实际上不需要调用 show() 显示图表,可以直接用 savefig() 将图表保存成图像文件。使用这种方法可以很容易编写批量输出图表的程序。
savefig() 的第一个参数可以是文件名,也可以是和 Python 的文件对象有相同调用接口的对象。例如可以将图像保存到 io.BytesIO 对象中,这样就得到了一个表示图像内容的字符串。这里需要使用 fmt 参数指定保存的图像格式。
import io
buf = io.BytesIO() # 创建一个用来保存图像内容的 BytesIO 对象
plt.savefig(buf, fmt="png") # 将图像以 png 格式保存到 buf 中
buf.getvalue()[:20] # 显示图像内容的前 20 字节
matplotlib 实际上是一套面向对象的绘图库,它所绘制的图表中的每个绘图元素,例如线条、文字、刻度等在内存中都有一个对象与之对应。为了方便快速绘图,matplotlib 通过 pyplot 模块提供了一套和 MATLAB 类似的绘图 API,将众多绘图对象所构成的复杂结构隐藏在这套 API 内部。我们只需要调用 pyplot 模块所提供的函数就可以实现快速绘图以及设置图表的各种细节。
为了将面向对象的绘图库包装成只使用函数的 API,pyplot 模块的内部保存了当前图表以及当前子图等信息。可以使用 gcf() 和 gca() 获得这两个对象,它们分别是'Get Current Figure'和'Get Current Axes'开头字母的缩写。gcf() 获得的是表示图表的 Figure 对象,而 gca() 获得的则是表示子图的 Axes 对象。
fig = plt.gcf()
axes = plt.gca()
print(fig, axes)
# 输出示例:Figure(640x320) Axes(0.125,0.1;0.775x0.8)
在 pyplot 模块中,许多函数都是对当前的 Figure 或 Axes 对象进行处理,例如前面介绍的 plot()、xlabel()、savefig() 等。我们可以在 IPython 中输入函数名并加'??',查看这些函数的源代码以了解它们是如何调用各种对象的方法进行绘图处理的。例如下面的例子查看 plot() 函数的源程序,可以看到 plot() 函数实际上会通过 gca() 获得当前的 Axes 对象 ax,然后再调用它的 plot() 方法来实现真正的绘图。
def plot(*args, **kwargs):
ax = gca()
...
try:
ret = ax.plot(*args, **kwargs)
...
finally:
ax.hold(washold)
matplotlib 所绘制图表的每个组成部分都和一个对象对应,可以通过调用这些对象的属性设置方法 set_*() 或者 pyplot 模块的属性设置函数 setp() 来设置它们的属性值。例如 plot() 返回一个元素类型为 Line2D 的列表,下面的例子设置 Line2D 对象的属性:
plt.figure(figsize=(4, 3))
x = np.arange(0, 5, 0.1)
line = plt.plot(x, 0.05*x*x)[0] # plot 返回一个列表
line.set_alpha(0.5) # 调用 Line2D 对象的 set_*() 方法来设置属性值
上面的例子中,通过调用 Line2D 对象的 set_alpha(),修改它在图表中对应曲线的透明度。下面的语句同时绘制正弦和余弦两条曲线,lines 是一个有两个 Line2D 对象的列表:
lines = plt.plot(x, np.sin(x), x, np.cos(x))
调用 setp() 可以同时配置多个对象的属性,这里同时设置两条曲线的颜色和线宽:
plt.setp(lines, color="r", linewidth=4.0)
同样,可以通过调用 Line2D 对象的 get_*() 或者通过 plt.getp() 来获取对象的属性值:
print(line.get_linewidth())
print(plt.getp(lines[0], "color")) # 返回 color 属性
# 输出:2.0
# 输出:r
注意 getp() 和 setp() 不同,它只能对一个对象进行操作,它有两种用法:
下面通过 getp() 查看 Figure 对象的属性:
f = plt.gcf()
plt.getp(f)
# 输出包含 agg_filter, alpha, animated, axes 等属性
Figure 对象的 axes 属性是一个列表,它保存图表中的所有子图对象。下面的程序查看当前图表的 axes 属性,它就是 gca() 所获得的当前子图对象:
print(plt.getp(f, "axes"), plt.getp(f, "axes")[0] is plt.gca())
# 输出:[<matplotlib.axes._subplots.AxesSubplot object at ...>] True
用 plt.getp() 可以继续获取 AxesSubplot 对象的属性,例如它的 lines 属性为子图中的 Line2D 对象列表:
alllines = plt.getp(plt.gca(), "lines")
print(allines, alllines[0] is line) # 其中的第一条曲线就是最开始绘制的那条曲线
# 输出:<a list of 3 Line2D objects> True
通过这种方法可以很容易查看对象的属性值以及各个对象之间的关系,找到需要配置的属性。因为 matplotlib 实际上是一套面向对象的绘图库,因此也可以直接获取对象的属性,例如:
print(f.axes, len(f.axes[0].lines))
# 输出:[<matplotlib.axes._subplots.AxesSubplot object at ...>] 3
一个 Figure 对象可以包含多个子图 (Axes),在 matplotlib 中用 Axes 对象表示一个绘图区域。在前面的例子中,Figure 对象只包括一个子图。可以使用 subplot() 快速绘制包含多个子图的图表,它的调用形式如下:
subplot(numRows, numCols, plotNum)
图表的整个绘图区域被等分为 numRows 行和 numCols 列,然后按照从左到右、从上到下的顺序对每个区域进行编号,左上区域的编号为 1。plotNum 参数指定所创建 Axes 对象的区域。如果 numRows、numCols 和 plotNum 三个参数都小于 10,则可以把它们缩写成一个整数,例如 subplot(323) 和 subplot(3,2,3) 的含义相同。如果新创建的子图和之前创建的子图区域有重叠的部分,之前的子图将被删除。
下面的程序创建 3 行 2 列共 6 个子图,并通过 axisbg 参数给每个子图设置不同的背景颜色:
for idx, color in enumerate("rgbyck"):
plt.subplot(321+idx, axisbg=color)
如果希望某个子图占据整行或整列,可以如下调用 subplot():
plt.subplot(221) # 第一行的左图
plt.subplot(222) # 第一行的右图
plt.subplot(212) # 第二整行
在绘图窗口的工具栏中,有一个名为'Configure Subplots'的按钮,单击它会弹出调节子图间距和子图与图表边框距离的对话框。也可以在程序中调用 subplots_adjust() 调节这些参数,它有 left、right、bottom、top、wspace 和 hspace 共 6 个参数,这些参数与对话框中的各个控件对应。参数的取值范围为 0 到 1,它们是以图表绘图区域的宽和高进行正规化之后的坐标或长度。
subplot() 返回它所创建的 Axes 对象,我们可以将这些对象用变量保存起来,然后用 sca() 交替让它们成为当前 Axes 对象,并调用 plot() 在其中绘图。如果需要同时绘制多幅图表,可以给 figure() 传递一个整数参数来指定 Figure 对象的序号。如果序号所指定的 Figure 对象已经存在,将不创建新的对象,而只是让它成为当前的 Figure 对象。
plt.figure(1) # 创建图表 1
plt.figure(2) # 创建图表 2
ax1 = plt.subplot(121) # 在图表 2 中创建子图 1
ax2 = plt.subplot(122) # 在图表 2 中创建子图 2
x = np.linspace(0, 3, 100)
for i in range(5): # 更新 xrange 为 range 以兼容 Python 3
plt.figure(1) # 选择图表 1
plt.plot(x, np.exp(i*x/3))
plt.sca(ax1) # 选择图表 2 的子图 1
plt.plot(x, np.sin(i*x))
plt.sca(ax2) # 选择图表 2 的子图 2
plt.plot(x, np.cos(i*x))
也可以不调用 sca() 指定当前子图,而直接调用 ax1 和 ax2 的 plot() 方法来绘图。
此外 subplots() 可以一次生成多个子图,并返回图表对象和保存子图对象的数组。在下面的例子中,axes 是一个形状为 (2, 3) 的数组,每个元素都是一个子图对象,可以利用 Python 的赋值功能将这个数组中的每个元素用一个变量表示:
fig, axes = plt.subplots(2, 3)
[a, b, c], [d, e, f] = axes
print(axes.shape)
print(b)
# 输出:(2, 3)
# 输出:Axes(0.398529,0.536364;0.227941x0.363636)
还可以调用 subplot2grid() 进行更复杂的表格布局。表格布局和在 Excel 或 Word 中绘制表格十分类似,其调用参数如下:
subplot2grid(shape, loc, rowspan=1, colspan=1, **kwargs)
其中,shape 为表示表格形状的元组:(行数,列数)。loc 为子图左上角所在的坐标:(行,列)。rowspan 和 colspan 分别为子图所占据的行数和列数。在下面的例子中,在 3×3 的网格上创建 5 个子图:
fig = plt.figure(figsize=(6, 6))
ax1 = plt.subplot2grid((3, 3), (0, 0), colspan=2)
ax2 = plt.subplot2grid((3, 3), (0, 2), rowspan=2)
ax3 = plt.subplot2grid((3, 3), (1, 0), rowspan=2)
ax4 = plt.subplot2grid((3, 3), (2, 1), colspan=2)
ax5 = plt.subplot2grid((3, 3), (1, 1))
绘制一幅图需要对许多对象的属性进行配置,例如颜色、字体、线型等。在前面的绘图程序中,并没有逐一对这些属性进行配置,而是直接采用 matplotlib 的默认配置。matplotlib 将这些默认配置保存在一个名为 matplotlibrc 的配置文件中,通过修改配置文件,可以修改图表的默认样式。
在 matplotlib 中可以使用多个 matplotlibrc 配置文件,它们的搜索顺序如下:顺序靠前的配置文件将会被优先采用。
.matplotlib 目录下,可以通过环境变量 MATPLOTLIBRC 修改它的位置。通过下面的语句可以获取用户配置路径:
from os import path
path.abspath(matplotlib.get_configdir())
通过下面的语句可以获得目前使用的配置文件的路径:
path.abspath(matplotlib.matplotlib_fname())
如果使用文本编辑器打开此配置文件,就会发现它实际上是一个字典。为了对众多的配置进行区分,字典的键根据配置的种类,用'.'分为多段。配置文件的读入可以使用 rc_params(),它返回一个配置字典:
print(matplotlib.rc_params())
# 输出包含 agg.path.chunksize, animation.avconv_args 等配置项
在 matplotlib 模块载入时会调用 rc_params(),并把得到的配置字典保存到 rcParams 变量中:
print(matplotlib.rcParams)
matplotlib 将使用 rcParams 字典中的配置进行绘图。用户可以直接修改此字典中的配置,所做的改变会反映到此后创建的绘图元素。例如下面的脚本所绘制的折线将带有圆形的点标识符:
matplotlib.rcParams["lines.marker"] = "o"
plt.plot([1,2,3,2])
为了方便对配置字典进行设置,可以使用 rc()。下面的例子同时配置点标识符、线宽和颜色:
matplotlib.rc("lines", marker="x", linewidth=2, color="red")
如果希望恢复到 matplotlib 载入时从配置文件读入的默认配置,可以调用 rcdefaults():
matplotlib.rcdefaults()
如果手工修改了配置文件,希望重新从配置文件载入最新的配置,可以调用:
matplotlib.rcParams.update(matplotlib.rc_params())
通过 pyplot 模块也可以使用 rcParams、rc 和 rcdefaults。
matplotlib.style 模块提供绘图样式切换功能,所有可选样式可以通过 available 获得:
from matplotlib import style
print(style.available)
# 输出:['dark_background', 'bmh', 'grayscale', 'ggplot', 'fivethirtyeight']
调用 use() 函数即可切换样式,例如下面使用 ggplot 样式绘图:
style.use("ggplot")
matplotlib 的默认配置文件中所使用的字体无法正确显示中文,可以通过下面几种方法设置中文字体:
在 matplotlib 中可以通过字体名指定字体,而每个字体名都与一个字体文件相对应。通过下面的程序可以获得所有可用字体的列表:
from matplotlib.font_manager import fontManager
fontManager.ttflist[:6]
ttflist 是 matplotlib 的系统字体列表。其中每个元素都是表示字体的 Font 对象,下面的程序显示了第一个字体文件的全路径和字体名,由路径可知它是 matplotlib 自带的字体:
print(fontManager.ttflist[0].name)
print(fontManager.ttflist[0].fname)
# 输出:cmss10
# 输出:C:\WinPython-32bit-2.7.9.2\python-2.7.9\lib\site-packages\matplotlib\mpl-data\fonts\ttf\cmss10.ttf
下面的程序使用字体列表中的字体显示中文文字:
import os
from os import path
fig = plt.figure(figsize=(8, 7))
ax = fig.add_subplot(111)
plt.subplots_adjust(0, 0, 1, 1, 0, 0)
plt.xticks([])
plt.yticks([])
x, y = 0.05, 0.05
fonts = [font.name for font in fontManager.ttflist if
path.exists(font.fname) and os.stat(font.fname).st_size > 1e6] # 保留大于 1MB 的字体文件
font = set(fonts)
dy = (1.0 - y) / (len(fonts) // 4 + (len(fonts)%4 != 0))
for font_name in fonts:
t = ax.text(x, y + dy / 2, u"中文字体",
{'fontname':font_name, 'fontsize':14}, transform=ax.transAxes)
ax.text(x, y, font_name, {'fontsize':12}, transform=ax.transAxes)
x += 0.25
if x >= 1.0:
y += dy
x = 0.05
plt.show()
由于 matplotlib 只搜索 TTF 字体文件,因此无法通过上述方法使用系统中安装的许多复合 TTC 字体文件。可以直接创建使用字体文件的 FontProperties 对象,并使用此对象指定图表中的各种文字的字体。下面是一个例子:
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14)
t = np.linspace(0, 10, 1000)
y = np.sin(t)
plt.close("all")
plt.plot(t, y)
plt.xlabel(u"时间", fontproperties=font)
plt.ylabel(u"振幅", fontproperties=font)
plt.title(u"正弦波", fontproperties=font)
plt.show()
还可以通过字体工具将 TTC 字体文件分解为多个 TTF 字体文件,并将其复制到系统的字体文件夹中。为了缩短启动时间,matplotlib 不会每次启动时都重新扫描所有的字体文件并创建字体列表,因此在复制完字体文件之后,需要运行下面的语句重新创建字体列表:
from matplotlib.font_manager import _rebuild
_rebuild()
还可以直接修改配置字典,设置默认字体,这样就不需要在每次绘制文字时设置字体了。例如:
plt.rcParams["font.family"] = "SimHei"
plt.plot([1,2,3])
plt.xlabel(0.5 ,0.5, u"中文字体")
或者修改上节介绍的配置文件,修改其中的 font.family 配置为 SimHei,注意 SimHei 是字体名,请读者运行前面的代码来查看系统中所有可用的中文字体名。
在实际使用中,开发者常遇到以下问题,建议参考以下解决方案:
plt.figure() 时增加 dpi 参数,如 plt.figure(dpi=300)。# -*- coding: utf-8 -*-。plt.plot(..., rasterized=True) 将图形部分栅格化,或使用 agg 后端。plt.show() 会阻塞程序。可以使用 plt.pause(0.001) 代替 show() 来实现动态刷新效果。通过掌握上述技巧,您可以更高效地使用 matplotlib 完成各类数据可视化任务,无论是简单的统计图表还是复杂的科学计算结果展示。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online