快速绘图¶
使用pyplot模块绘图
matplotlib的pyplot模块提供了和MATLAB类似的绘图API,方便用户快速绘制二维图表。我们先看一个简单的例子:
05-matplotlib/matplotlib_simple_plot.py
用pylab库快速绘图
import numpy as np
import matplotlib.pyplot as plt ❶
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() ❻
程序的输出如【图:使用pyplot模块快速将数据绘制成曲线图】所示。
❶首先载入matplotlib的绘图模块pyplot,并且重命名为plt。
pylab模块
matplotlib还提供了一个名为pylab的模块,其中包括了许多NumPy和pyplot模块中常用的函数,方便用户快速进行计算和绘图,十分适合在IPython交互式环境中使用。本书使用下面的方式载入pylab模块:
>>> import pylab as pl
❷调用figure()创建一个Figure(图表)对象,并且它将成为当前Figure对象。也可以不创建Figure对象直接调用接下来的plot()进行绘图,这时matplotlib会自动创建一个Figure对象。figsize参数指定Figure对象的宽度和高度,其单位为英寸。此外还可以用dpi参数指定Figure对象的分辨率,即每英寸所表示的像素数,这里使用缺省值80。因此本例中所创建的Figure对象的宽度为“8*80 = 640”个像素。但是在显示出绘图窗口之后,用工具栏中的保存按钮将图表保存为图像时,所保存的图像的大小是“800*400”像素。这是因为保存图像时会使用不同的dpi设置。这个设置保存在matplotlib的配置文件中,我们可以通过如下语句查看它的值:
>>> import matplotlib
>>> matplotlib.rcParams["savefig.dpi"]
100
因为保存图像时的DPI设置为100,因此所保存的图像的宽度是“8*100 = 800”个像素。rcParams是一个字典,其中保存着从配置文件读入的所有配置,在调用各种绘图函数时,这些配置将会作为各种参数的缺省值。后面我们还会对matplotlib的配置文件进行详细介绍。
❸创建Figure对象之后,接下来调用plot()在当前的Figure对象中绘图。实际上plot()是在Axes(子图)对象上绘图,如果当前的Figure对象中没有Axes对象,将会为之创建一个几乎充满整个图表的Axes对象,并且使此Axes对象成为当前的Axes对象。plot()的前两个参数是分别表示X、Y轴数据的对象,这里使用的是NumPy数组。使用关键字参数可以指定所绘制的曲线的各种属性:
- label:给曲线指定一个标签名称,此标签将在图示中显示。如果标签字符串的前后有字符’$’,则matplotlib会使用其内嵌的LaTex引擎将其显示为数学公式。
- color:指定曲线的颜色,颜色可以用英文单词,或者以’#’字符开头的三个16进制数,例如’#ff0000’表示红色。或者使用值在0到1范围之内的三个元素的元组表示,例如(1.0, 0.0, 0.0)也表示红色。
- linewidth:指定曲线的宽度,可以不是整数,也可以使用缩写形式的参数名lw。
❹直接通过第三个参数’b–’指定曲线的颜色和线型,它通过一些易记的符号指定曲线的样式。其中’b’表示蓝色,’–’表示线型为虚线。在IPython中输入“plt.plot?”可以查看格式化字符串以及各个参数的详细说明。
❺接下来通过一系列函数设置当前Axes对象的各个属性:
- xlabel、ylabel:分别设置X、Y轴的标题文字。
- title:设置子图的标题。
- xlim、ylim:分别设置X、Y轴的显示范围。
- legend:显示图示,即图中表示每条曲线的标签(label)和样式的矩形区域。
❻最后调用plt.show()显示出绘图窗口。在通常的运行情况下,show()将会阻塞程序的运行,直到用户关闭绘图窗口。然而在带“-wthread”等参数的IPython环境下,show()不会等待窗口关闭。
还可以调用plt.savefig()将当前的Figure对象保存成图像文件,图像格式由图像文件的扩展名决定。下面的程序将当前的图表保存为“test.png”,并且通过dpi参数指定图像的分辨率为120,因此输出图像的宽度为“8*120 = 960”个像素。
>>> run matplotlib_simple_plot.py
>>> plt.savefig("test.png", dpi=120)
savefig()的第一个参数可以是文件名,也可以是和Python的文件对象有相同调用接口的对象。例如可以将图像保存到StringIO对象中,这样就得到了一个表示图像内容的字符串。这里需要使用fmt参数指定保存的图像格式。
>>> from StringIO import StringIO
>>> buf = StringIO() # 创建一个用来保存图像内容的StringIO对象
>>> plt.savefig(buf, fmt="png") # 将图像以png格式保存进buf中
>>> buf.getvalue()[:20] # 显示图像内容的前20个字节
'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x03 '
面向对象方式绘图
matplotlib实际上是一套面向对象的绘图库,它所绘制的图表中的每个绘图元素,例如线条、文字、刻度等在内存中都有一个对象与之对应。为了方便快速绘图matplotlib通过pyplot模块提供了一套和MATLAB类似的绘图API,将众多绘图对象所构成的复杂结构隐藏在这套API内部。我们只需要调用pyplot模块所提供的函数就可以实现快速绘图以及设置图表的各种细节。pyplot模块虽然用法简单,但不适合在较大的应用程序中使用,因此本章将着重介绍如何使用matplotlib的面向对象的方式编写绘图程序。
为了将面向对象的绘图库包装成只使用函数的调用接口,pyplot模块的内部保存了当前图表以及当前子图等信息。当前的图表和子图可以使用gcf()和gca()获得,它们分别是“Get Current Figure”和“Get Current Axis”的开头字母缩写。gcf()获得的是表示图表的Figure对象,而gca()则获得的是表示子图的Axes对象。下面我们在IPython中运行上节的“matplotlib_simple_plot.py”程序,然后调用gcf()和gca()查看当前的Figure和Axes对象。
>>> run matplotlib_simple_plot.py
>>> fig = plt.gcf()
>>> axes = plt.gca()
>>> fig
<matplotlib.figure.Figure object at 0x04B30090>
>>> axes
<matplotlib.axes.AxesSubplot object at 0x04BD8E70>
在pyplot模块中,许多函数都是对当前的Figure或Axes对象进行处理,例如前面所介绍的plot()、xlabel()、savefig()等。我们可以在IPython中输入函数名并加”??”,查看这些函数的源代码了解它们是如何调用各种对象的方法进行绘图处理的。例如下面的例子查看plot()的源程序,可以看到plot()实际上会通过gca()获得当前的Axes对象ax,然后再调用它的plot()方法实现真正的绘图。请读者使用类似的方法查看pyplot模块的其它函数是如何对各种绘图对象进行包装的。
>>> plt.plot??
...
def plot(*args, **kwargs):
ax = gca()
...
try:
ret = ax.plot(*args, **kwargs)
...
finally:
ax.hold(washold)
配置属性
matplotlib所绘制的图表的每个组成部分都和一个对象对应,我们可以通过调用这些对象的属性设置方法set_*()或者pyplot模块的属性设置函数setp()设置它们的属性值。例如plot()返回一个元素类型为Line2D的列表,下面的例子设置Line2D对象的属性:
>>> x = np.arange(0, 5, 0.1)
>>> line = plt.plot(x, x*x)[0] # plot返回一个列表
>>> line.set_antialiased(False) # 调用Line2D对象的set_*()方法设置属性值
上面的例子中,通过调用Line2D对象的set_antialiased(False),关闭了它在图表中对应的曲线的反锯齿效果。下面的语句同时绘制正弦和余弦两条曲线,lines是一个有两个Line2D对象的列表:
>>> lines = plt.plot(x, np.sin(x), x, np.cos(x))
调用setp()可以同时配置多个对象的属性,这里我们同时设置两条曲线的颜色和线宽:
>>> plt.setp(lines, color="r", linewidth=2.0)
同样,可以通过调用Line2D对象的get_*(),或者plt.getp()获取对象的属性值:
>>> line.get_linewidth()
1.0
>>> plt.getp(lines[0], "color") # 返回color属性
'r'
>>> plt.getp(lines[1]) # 输出全部属性
alpha = 1.0
animated = False
antialiased or aa = True
axes = Axes(0.125,0.1;0.775x0.8)
...
注意getp()和setp()不同,它只能对一个对象进行操作,它有两种用法:
- 指定属性名:返回对象的某个属性的值
- 不指定属性名:输出对象的所有属性和值
下面通过getp()查看Figure对象的属性:
>>> f = plt.gcf()
>>> plt.getp(f)
alpha = 1.0
animated = False
...
Figure对象的axes属性是一个列表,它保存图表中的所有子图对象。下面的程序查看当前图表的axes属性,可以看出其中包含gca()所获得的当前子图对象:
>>> plt.getp(f, "axes")
[<matplotlib.axes.AxesSubplot object at 0x05CDD170>]
>>> plt.gca()
<matplotlib.axes.AxesSubplot object at 0x05CDD170>
用plt.getp()可以继续获取AxesSubplot对象的属性,例如它的lines属性为子图中的Line2D对象列表:
>>> alllines = plt.getp(plt.gca(), "lines")
>>> alllines
<a list of 3 Line2D objects>
>>> alllines[0] == line # 其中的第一条曲线就是最开始绘制的那条曲线
True
通过这种方法可以很容易查看对象的属性值以及各个对象之间的关系,找到需要配置的属性。
因为matplotlib实际上是一套面向对象的绘图库,因此也可以直接获取对象的属性,例如:
>>> f.axes
[<matplotlib.axes.AxesSubplot object at 0x05CDD170>]
>>> f.axes[0].lines
<a list of 3 Line2D objects>
绘制多子图
一个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)的含义相同。如果新创建的子图和之前创建的子图区域有重叠的部分,则之前的子图将被删除。
下面的程序创建如【图:用subplot()在当前的Figure对象中创建6个子图】所示的3行2列共6个子图,并通过axisbg参数给每个子图设置不同的背景颜色。
for idx, color in enumerate("rgbyck"):
plt.subplot(321+idx, axisbg=color)
plt.show()
如果希望某个子图占据整行或者整列,可以如下调用subplot():
plt.subplot(221) # 第一行的左图
plt.subplot(222) # 第一行的右图
plt.subplot(212) # 第二整行
plt.show()
程序的输出如【图:将Figure分为三个子图】所示。
在绘图窗口的工具栏中,有一个名为“Configure Subplots”的按钮,点击它弹出调节子图间距和子图与图表边框距离的对话框。也可以在程序中调用subplots_adjust()调节这些参数,它有left、right、bottom、top、wspace和hspace等六个参数,这些参数与对话框中的各个控件对应。参数的取值范围为0到1,它们是以图表绘图区域的宽和高进行正规化之后的坐标或者长度。
subplot()返回它所创建的Axes对象,我们可以将它用变量保存起来,然后用sca()交替让它们成为当前Axes对象,并调用plot()在其中绘图。如果需要同时绘制多幅图表,可以给figure()传递一个整数参数指定Figure对象的序号,如果序号所指定的Figure对象已经存在,将不创建新的对象,而只是让它成为当前的Figure对象。下面的程序演示了依次在不同图表的不同子图中绘制曲线。
05-matplotlib/matplotlib_multi_figure.py
同时在多幅图表、多个子图中进行绘图
import numpy as np
import matplotlib.pyplot as plt
plt.figure(1) # 创建图表1
plt.figure(2) # 创建图表2
ax1 = plt.subplot(211) # 在图表2中创建子图1
ax2 = plt.subplot(212) # 在图表2中创建子图2
x = np.linspace(0, 3, 100)
for i in xrange(5):
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))
plt.show()
首先通过figure()创建了两个图表,它们的序号分别为1和2。然后在图表2中创建了上下并排的两个子图,并用变量ax1和ax2保存。
在循环中,❶先调用figure(1)让图表1成为当前图表,并在其中绘图。❷然后调用sca(ax1)和sca(ax2)分别让子图ax1和ax2成为当前子图,并在其中绘图。当它们成为当前子图时,包含它们的图表2也自动成为当前图表,因此不需要调用figure(2)。这样依次在图表1和图表2的两个子图之间切换,逐步在其中添加新的曲线。其效果如【图:同时在多幅图表、多个子图中进行绘图】所示。
配置文件
绘制一幅图需要对许多对象的属性进行配置,例如颜色、字体、线型等等。我们在绘图时,并没有逐一对这些属性进行配置,许多都直接采用了matplotlib的缺省配置。matplotlib将这些缺省配置保存在一个名为“matplotlibrc”的配置文件中,通过修改配置文件,我们可以修改图表的缺省样式。
在matplotlib中可以使用多个“matplotlibrc”配置文件,它们的搜索顺序如下,顺序靠前的配置文件将会被优先采用。
- 当前路径:程序的当前路径。
- 用户配置路径:通常在用户文件夹的“.matplotlib”目录下,可以通过环境变量MATPLOTLIBRC修改它的位置。
- 系统配置路径:保存在matplotlib的安装目录下的mpl-data中。
通过下面的语句可以获取用户配置路径:
>>> import matplotlib
>>> matplotlib.get_configdir()
'C:\\Documents and Settings\\用户名\\.matplotlib'
通过下面的语句可以获得目前使用的配置文件的路径:
>>> import matplotlib
>>> matplotlib.matplotlib_fname()
'C:\\Python26\\lib\\site-packages\\matplotlib\\mpl-data\\matplotlibrc'
由于在当前路径和用户配置路径中都没有找到配置文件,因此最后使用的是系统配置路径下的配置文件。如果读者将matplotlibrc复制一份到脚本的当前目录(例如,c:\zhang\doc)下:
>>> import os
>>> os.getcwd()
'C:\\zhang\\doc'
复制配置文件之后再查看配置文件的路径,就会发现它变为了当前目录下的配置文件:
>>> matplotlib.matplotlib_fname()
'C:\\zhang\\doc\\matplotlibrc'
如果读者使用文本编辑器打开此配置文件,就会发现它实际上是一个字典。为了对众多的配置进行区分,字典的键根据配置的种类,用“.”分为多段。
配置文件的读入可以使用rc_params(),它返回一个配置字典:
>>> matplotlib.rc_params()
{'agg.path.chunksize': 0,
'axes.axisbelow': False,
'axes.edgecolor': 'k',
'axes.facecolor': 'w',
... ...
在matplotlib模块载入时会调用rc_params(),并把得到的配置字典保存到rcParams变量中:
>>> matplotlib.rcParams
{'agg.path.chunksize': 0,
'axes.axisbelow': False,
... ...
matplotlib将使用rcParams字典中的配置进行绘图。用户可以直接修改此字典中的配置,所做的改变会反映到此后创建的绘图元素。例如下面的代码所绘制的折线将带有圆形的点标识符:
>>> matplotlib.rcParams["lines.marker"] = "o"
>>> plt.plot([1,2,3,2])
>>> plt.show()
为了方便对配置字典进行设置,可以使用rc()。下面的例子同时配置点标识符、线宽和颜色:
>>> matplotlib.rc("lines", marker="x", linewidth=2, color="red")
如果希望恢复到缺省的配置(matplotlib载入时从配置文件读入的配置),可以调用rcdefaults()。
>>> matplotlib.rcdefaults()
如果手工修改了配置文件,希望重新从配置文件载入最新的配置,可以调用:
>>> matplotlib.rcParams.update( matplotlib.rc_params() )
在图表中显示中文
matplotlib的缺省配置文件中所使用的字体无法正确显示中文。为了让图表能正确显示中文,可以有几种解决方案。
- 在程序中直接指定字体。
- 在程序开头修改配置字典rcParams。
- 修改配置文件。
在matplotlib中可以通过字体名指定字体,而每个字体名都与一个字体文件相对应。通过下面的程序可以获得所有可用的字体列表:
>>> from matplotlib.font_manager import fontManager
>>> fontManager.ttflist
[<Font 'cmex10' (cmex10.ttf) normal normal 400 normal>,
<Font 'Bitstream Vera Sans Mono' (VeraMoBd.ttf) normal normal 700 normal>,
...
]
fontManager.ttflist是matplotlib的系统字体索引列表。其中的每个元素都是表示字体的Font对象。例如由第一个Font对象可知,字体名”cmex10”与字体文件“cmex10.ttf”相对应。下面的语句获得字体文件的全路径和字体名:
>>> fontManager.ttflist[0].name
'cmex10'
>>> fontManager.ttflist[0].fname
'C:\\Python26\\lib\\site-packages\\matplotlib\\mpl-data\\fonts\\ttf\\cmex10.ttf'
由字体文件的路径可知’cmex10’是matplotlib自带的字体。下面的程序利用字体索引列表中的字体显示中文文字,其效果如【图:显示系统中的所有中文字体】所示。
05-matplotlib/matplotlib_fonts.py
显示所有的中文字体
from matplotlib.font_manager import fontManager
import matplotlib.pyplot as plt
import os
import os.path
fig = plt.figure(figsize=(12,6))
ax = fig.add_subplot(111)
plt.subplots_adjust(0, 0, 1, 1, 0, 0)
plt.xticks([])
plt.yticks([])
x, y = 0.05, 0.08
fonts = [font.name for font in fontManager.ttflist if
os.path.exists(font.fname) and os.stat(font.fname).st_size>1e6] ❶
font = set(fonts)
dy = (1.0-y)/(len(fonts)/4 + (len(fonts)%4!=0))
for font in fonts:
t = ax.text(x, y, u"中文字体", {'fontname':font, 'fontsize':14}, transform=ax.transAxes) ❷
ax.text(x, y-dy/2, font, transform=ax.transAxes)
x += 0.25
if x >= 1.0:
y += dy
x = 0.05
plt.show()
❶利用os模块中的stat()获取字体文件的大小,并保留字体索引列表中所有大于1M字节的字体文件。由于中文字体文件通常都很大,因此使用这种方法可以粗略地找出所有的中文字体文件。
❷调用子图对象的text()在其中添加文字,注意文字必须是Unicode字符串。通过一个描述字体的字典指定文字的字体:’fontname’键所对应的值就是字体名。
由于matplotlib只搜索TTF字体文件,因此无法通过上述方法使用Windows的Fonts目录下的许多复合字体文件(*.ttc)。可以直接创建使用字体文件的FontProperties对象,并使用此对象指定图表中的各种文字的字体。下面是一个例子:
05-matplotlib/matplotlib_simsun_font.py
使用TTC字体文件
from matplotlib.font_manager import FontProperties
import matplotlib.pyplot as plt
import numpy as np
font = FontProperties(fname=r"c:\windows\fonts\simsun.ttc", size=14) ❶
t = np.linspace(0, 10, 1000)
y = np.sin(t)
plt.plot(t, y)
plt.xlabel(u"时间", fontproperties=font) ❷
plt.ylabel(u"振幅", fontproperties=font)
plt.title(u"正弦波", fontproperties=font)
plt.show()
❶创建一个描述字体属性的FontProperties对象,并设置其fname属性为字体文件的绝对路径。❷通过fontproperties参数将FontProperties对象传递给显示文字的函数。
还可以通过字体工具将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”配置为:
font.family : SimHei
注意“SimHei”是字体名,请读者运行“matplotlib_fonts.py”查看系统中所有可用的中文字体名。