matplotlib API入门

使用matplotlib的办法有很多种,最常用的方式是Pylab模式的IPython(ipython —pylab)。这样会将IPython配置为使用你所指定的matplotlib GUI后端(Tk、wxPython、PyQt、Mac OS X native、GTK)。对大部分用户而言,默认的后端就已经够用了。Pylab模式还会向IPython引入一大堆模块和函数以提供一种更接近于MATLAB的界面(见图8-1)。绘制一张简单的图表即可测试是否一切准备就绪:

  1. plot(np.arange(10))

matplotlib API入门 - 图1

图8-1:一张比较复杂的matplotlib金融曲线图

如果一切都没有问题,就会弹出一个新窗口,其中绘制的是一条直线。你可以用鼠标或输入close()来关闭它。matplotlib API函数(如plot和close)都位于matplotlib.pyplot模块中,其通常的引入约定是:

  1. import matplotlib.pyplot as plt

虽然pandas的绘图函数(稍后介绍)能够处理许多普通的绘图任务,但如果需要自定义一些高级功能的话就必须学习matplotlib API。

注意: 虽然本书没有详细地讨论matplotlib的各种功能,但足以将你引入门。matplotlib的示例库和文档是成为绘图高手的最佳学习资源。

Figure和Subplot

matplotlib的图像都位于Figure对象中。你可以用plt.figure创建一个新的Figure:

  1. In [13]: fig = plt.figure()

这时会弹出一个空窗口。plt.figure有一些选项,特别是figsize,它用于确保当图片保存到磁盘时具有一定的大小和纵横比。matplotlib中的Figure还支持一种MATLAB式的编号架构(例如plt.figure(2))。通过plt.gcf()即可得到当前Figure的引用。

不能通过空Figure绘图。必须用add_subplot创建一个或多个subplot才行:

  1. In [14]: ax1 = fig.add_subplot(2, 2, 1)

这条代码的意思是:图像应该是2×2的,且当前选中的是4个subplot中的第一个(编号从1开始)。如果再把后面两个subplot也创建出来,最终得到的图像如图8-2所示。

  1. In [15]: ax2 = fig.add_subplot(2, 2, 2)
  2.  
  3. In [16]: ax3 = fig.add_subplot(2, 2, 3)

matplotlib API入门 - 图2

图8-2:带有三个subplot的Figure

如果这时发出一条绘图命令(如plt.plot([1.5,3.5,-2,1.6])),matplotlib就会在最后一个用过的subplot(如果没有则创建一个)上进行绘制。因此,如果我们执行下列命令,你就会得到如图8-3所示的结果:

  1. In [17]: from numpy.random import randn
  2.  
  3. In [18]: plt.plot(randn(50).cumsum(), 'k--')

matplotlib API入门 - 图3

图8-3:绘制一次之后的图像

"k—"是一个线型选项,用于告诉matplotlib绘制黑色虚线图。上面那些由fig.add_subplot所返回的对象是AxesSubplot对象,直接调用它们的实例方法就可以在其他空着的格子里面画图了,如图8-4所示:

  1. In [19]: _ = ax1.hist(randn(100), bins=20, color='k', alpha=0.3)
  2.  
  3. In [20]: ax2.scatter(np.arange(30), np.arange(30) + 3 * randn(30))

matplotlib API入门 - 图4

图8-4:继续绘制两次之后的图像

你可以在matplotlib的文档中找到各种图表类型。由于根据特定布局创建Figure和subplot是一件非常常见的任务,于是便出现了一个更为方便的方法(plt.subplots),它可以创建一个新的Figure,并返回一个含有已创建的subplot对象的NumPy数组:

  1. In [22]: fig, axes = plt.subplots(2, 3)
  2.  
  3. In [23]: axes
  4. Out[23]:
  5. array([[Axes(0.125,0.536364;0.227941x0.363636),
  6. Axes (0.398529,0.536364;0.227941x0.363636),
  7. Axes (0.672059,0.536364;0.227941x0.363636)],
  8. [Axes (0.125,0.1;0.227941x0.363636),
  9. Axes (0.398529,0.1;0.227941x0.363636),
  10. Axes (0.672059,0.1;0.227941x0.363636)]], dtype=object)

这是非常实用的,因为可以轻松地对axes数组进行索引,就好像是一个二维数组一样,例如,axes[0,1]。你还可以通过sharex和sharey指定subplot应该具有相同的X轴或Y轴。在比较相同范围的数据时,这也是非常实用的,否则,matplotlib会自动缩放各图表的界限。有关该方法的更多信息,请参见表8-1。

matplotlib API入门 - 图5

调整subplot周围的间距

默认情况下,matplotlib会在subplot外围留下一定的边距,并在subplot之间留下一定的间距。间距跟图像的高度和宽度有关,因此,如果你调整了图像大小(不管是编程还是手工),间距也会自动调整。利用Figure的subplots_adjust方法可以轻而易举地修改间距,此外,它也是个顶级函数:

  1. subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=None,
  2. hspace=None)

wspace和hspace用于控制宽度和高度的百分比,可以用作subplot之间的间距。下面是一个简单的例子,其中我将间距收缩到了0(如图8-5所示):

  1. fig, axes = plt.subplots(2, 2, sharex=True, sharey=True)
  2. for i in range(2):
  3. for j in range(2):
  4. axes[i, j].hist(randn(500), bins=50, color='k', alpha=0.5)
  5. plt.subplots_adjust(wspace=0, hspace=0)

matplotlib API入门 - 图6

图8-5:各subplot之间没有间距

不难看出,其中的轴标签重叠了。matplotlib不会检查标签是否重叠,所以对于这种情况,你只能自己设定刻度位置和刻度标签。后面几节将会详细介绍该内容。

颜色、标记和线型

matplotlib的plot函数接受一组X和Y坐标,还可以接受一个表示颜色和线型的字符串缩写。例如,要根据x和y绘制绿色虚线,你可以执行如下代码:

  1. ax.plot(x, y, 'g--')

这种在一个字符串中指定颜色和线型的方式非常方便。通过下面这种更为明确的方式也能得到同样的效果:

  1. ax.plot(x, y, linestyle='--', color='g')

常用的颜色都有一个缩写词,要使用其他任意颜色则可以通过指定其RGB值的形式使用(例如,'#CECECE')。完整的linestyle列表请参见plot的文档。

线型图还可以加上一些标记(marker),以强调实际的数据点。由于matplotlib创建的是连续的线型图(点与点之间插值),因此有时可能不太容易看出真实数据点的位置。标记也可以放到格式字符串中,但标记类型和线型必须放在颜色后面(如图8-6所示):

  1. In [28]: plt.plot(randn(30).cumsum(), 'ko--')

matplotlib API入门 - 图7

图8-6:带有标记的线型图示例

还可以将其写成更为明确的形式:

  1. plot(randn(30).cumsum(), color='k', linestyle='dashed', marker='o')

在线型图中,非实际数据点默认是按线性方式插值的。可以通过drawstyle选项修改:

  1. In [30]: data = randn(30).cumsum()
  2.  
  3. In [31]: plt.plot(data, 'k--', label='Default')
  4. Out[31]: [<matplotlib.lines.Line2D at 0x461cdd0>]
  5.  
  6. In [32]: plt.plot(data, 'k-', drawstyle='steps-post', label='steps-post')
  7. Out[32]: [<matplotlib.lines.Line2D at 0x461f350>]
  8.  
  9. In [33]: plt.legend(loc='best')

刻度、标签和图例

对于大多数的图表装饰项,其主要实现方式有二:使用过程型的pyplot接口(MATLAB用户非常熟悉)以及更为面向对象的原生matplotlib API。

pyplot接口的设计目的就是交互式使用,含有诸如xlim、xticks和xticklabels之类的方法。它们分别控制图表的范围、刻度位置、刻度标签等。其使用方式有以下两种:

·调用时不带参数,则返回当前的参数值译注1。例如,plt.xlim()返回当前的X轴绘图范围。

·调用时带参数,则设置参数值。因此,plt.xlim([0,10])会将X轴的范围设置为0到10。

matplotlib API入门 - 图8

图8-7:不同drawstyle选项的线型图

所有这些方法都是对当前或最近创建的AxesSubplot起作用的。它们各自对应subplot对象上的两个方法,以xlim为例,就是ax.get_xlim和ax.set_xlim。我更喜欢使用subplot的实例方法(因为我喜欢明确的事情,而且在处理多个subplot时这样也更清楚一些)。当然你完全可以选择自己觉得方便的那个。

设置标题、轴标签、刻度以及刻度标签

为了说明轴的自定义,我将创建一个简单的图像并绘制一段随机漫步(如图8-8所示):

  1. In [34]: fig = plt.figure(); ax = fig.add_subplot(1, 1, 1)
  2.  
  3. In [35]: ax.plot(randn(1000).cumsum())

matplotlib API入门 - 图9

图8-8:用于演示xticks的简单线型图

要修改X轴的刻度,最简单的办法是使用set_xticks和set_xticklabels。前者告诉matplotlib要将刻度放在数据范围中的哪些位置,默认情况下,这些位置也就是刻度标签。但我们可以通过set_xticklabels将任何其他的值用作标签:

  1. In [36]: ticks = ax.set_xticks([0, 250, 500, 750, 1000])
  2.  
  3. In [37]: labels = ax.set_xticklabels(['one', 'two', 'three', 'four', 'five'],
  4. ...: rotation=30, fontsize='small')

最后,再用set_xlabel为X轴设置一个名称,并用set_title设置一个标题:

  1. In [38]: ax.set_title('My first matplotlib plot')
  2. Out[38]: <matplotlib.text.Text at 0x7f9190912850>
  3.  
  4. In [39]: ax.set_xlabel('Stages')

最终结果如图8-9所示。Y轴的修改方式与此类似,只需将上述代码中的x替换为y即可。

添加图例

图例(legend)是另一种用于标识图表元素的重要工具。添加图例的方式有二。最简单的是在添加subplot的时候传入label参数:

  1. In [40]: fig = plt.figure(); ax = fig.add_subplot(1, 1, 1)
  2.  
  3. In [41]: ax.plot(randn(1000).cumsum(), 'k', label='one')
  4. Out[41]: [<matplotlib.lines.Line2D at 0x4720a90>]

matplotlib API入门 - 图10

图8-9:用于演示xticks的简单线型图

  1. In [42]: ax.plot(randn(1000).cumsum(), 'k--', label='two')
  2. Out[42]: [<matplotlib.lines.Line2D at 0x4720f90>]
  3.  
  4. In [43]: ax.plot(randn(1000).cumsum(), 'k.', label='three')
  5. Out[43]: [<matplotlib.lines.Line2D at 0x4723550>]

在此之后,你可以调用ax.legend()或plt.legend()来自动创建图例:

  1. In [44]: ax.legend(loc='best')

如图8-10所示。loc告诉matplotlib要将图例放在哪。如果你不是吹毛求疵的话,"beat"是不错的选择,因为它会选择最不碍事的位置。要从图例中去除一个或多个元素,不传入label或传入label='nolegend'即可。

注解以及在Subplot上绘图

除标准的图表对象之外,你可能还希望绘制一些自定义的注解(比如文本、箭头或其他图形等)。

注解可以通过text、arrow和annotate等函数进行添加。text可以将文本绘制在图表的指定坐标(x,y),还可以加上一些自定义格式:

  1. ax.text(x, y, 'Hello world!',
  2. family='monospace', fontsize=10)

matplotlib API入门 - 图11

图8-10:带有三条线以及图例的简单线型图

注解中可以既含有文本也含有箭头。例如,我们根据2007年以来的标准普尔500指数收盘价格(来自Yahoo!Finance)绘制一张曲线图,并标出2008年到2009年金融危机期间的一些重要日期。结果如图8-11所示:

matplotlib API入门 - 图12

图8-11:2008-2009年金融危机期间的重要日期

  1. from datetime import datetime
  2.  
  3. fig = plt.figure()
  4. ax = fig.add_subplot(1, 1, 1)
  5.  
  6. data = pd.read_csv('ch08/spx.csv', index_col=0, parse_dates=True)
  7. spx = data['SPX']
  8.  
  9. spx.plot(ax=ax, style='k-')
  10.  
  11. crisis_data = [
  12. (datetime(2007, 10, 11), 'Peak of bull market'),
  13. (datetime(2008, 3, 12), 'Bear Stearns Fails'),
  14. (datetime(2008, 9, 15), 'Lehman Bankruptcy')
  15. ]
  16.  
  17. for date, label in crisis_data:
  18. ax.annotate(label, xy=(date, spx.asof(date) + 50),
  19. xytext=(date, spx.asof(date) + 200),
  20. arrowprops=dict(facecolor='black'),
  21. horizontalalignment='left', verticalalignment='top')
  22.  
  23. # 放大到2007-2010
  24. ax.set_xlim(['1/1/2007', '1/1/2011'])
  25. ax.set_ylim([600, 1800])
  26.  
  27. ax.set_title('Important dates in 2008-2009 financial crisis')

更多有关注解的示例,请访问matplotlib的在线示例库。

图形的绘制要麻烦一些。matplotlib有一些表示常见图形的对象。这些对象被称为块(patch)。其中有些可以在matplotlib.pyplot中找到(如Rectangle和Circle),但完整集合位于matplotlib.patches。

要在图表中添加一个图形,你需要创建一个块对象shp,然后通过ax.add_patch(shp)将其添加到subplot中(如图8-12所示):

  1. fig = plt.figure()
  2. ax = fig.add_subplot(1, 1, 1)
  3.  
  4. rect = plt.Rectangle((0.2, 0.75), 0.4, 0.15, color='k', alpha=0.3)
  5. circ = plt.Circle((0.7, 0.2), 0.15, color='b', alpha=0.3)
  6. pgon = plt.Polygon([[0.15, 0.15], [0.35, 0.4], [0.2, 0.6]], color='g', alpha=0.5)
  7.  
  8.  
  9. ax.add_patch(rect)
  10. ax.add_patch(circ)
  11. ax.add_patch(pgon)

如果查看许多常见图表对象的具体实现代码,你就会发现它们其实就是由块组装而成的。

matplotlib API入门 - 图13

图8-12:由三个块图形组成的图

将图表保存到文件

利用plt.savefig可以将当前图表保存到文件。该方法相当于Figure对象的实例方法savefig。例如,要将图表保存为SVG文件,你只需输入:

  1. plt.savefig('figpath.svg')

文件类型是通过文件扩展名推断出来的。因此,如果你使用的是.pdf,就会得到一个PDF文件。我在发布图片时最常用到两个重要的选项是dpi(控制“每英寸点数”分辨率)和bbox_inches(可以剪除当前图表周围的空白部分)。要得到一张带有最小白边且分辨率为400DPI的PNG图片,你可以:

  1. plt.savefig('figpath.png', dpi=400, bbox_inches='tight')

savefig并非一定要写入磁盘,也可以写入任何文件型的对象,比如StringIO:

  1. from io import StringIO
  2. buffer = StringIO()
  3. plt.savefig(buffer)
  4. plot_data = buffer.getvalue()

这对在Web上提供动态生成的图片是很实用的。

Figure.savefig方法的参数及说明如表8-2所示。

matplotlib API入门 - 图14

matplotlib配置

matplotlib自带一些配色方案,以及为生成出版质量的图片而设定的默认配置信息。幸运的是,几乎所有默认行为都能通过一组全局参数进行自定义,它们可以管理图像大小、subplot边距、配色方案、字体大小、网格类型等。操作matplotlib配置系统的方式主要有两种。第一种是Python编程方式,即利用rc方法。比如说,要将全局的图像默认大小设置为10×10,你可以执行:

  1. plt.rc('figure', figsize=(10, 10))

rc的第一个参数是希望自定义的对象,如'figure'、'axes'、'xtick'、'ytick'、'grid'、'legend'等。其后可以跟上一系列的关键字参数。最简单的办法是将这些选项写成一个字典:

  1. font_options = {'family' : 'monospace',
  2. 'weight' : 'bold',
  3. 'size' : 'small'}
  4. plt.rc('font', **font_options)

要了解全部的自定义选项,请查阅matplotlib的配置文件matplotlibrc(位于matplotlib/mpl-data目录中)。如果对该文件进行了自定义,并将其放在你自己的.matplotlibrc目录译注2中,则每次使用matplotlib时就会加载该文件。

译注1:前面的参数是argument,后面的参数是parameter。我觉得后面那个parameter不太合适,但又实在想不出更好的表达方式。各位读者可以把后面那个parameter理解为“当前配置值”。下面那条也是如此。

译注2:正确的目录名是.matplotlib。