title: matplotlib-绘制精美的图表 create: 2016.12.7 modified: 2016.12.7 tags: python pylab模块 plot
9
[TOC]
matplotlib 是python最著名的绘图库,它提供了一整套和matlab相似的命令API,十分适合交互式地进行制图。而且也可以方便地将它作为绘图控件,嵌入GUI应用程序中。
它的文档相当完备,并且Gallery页面中有上百幅缩略图,打开之后都有源程序。因此如果你需要绘制某种类型的图,只需要在这个页面中浏览/复制/粘贴一下,基本上都能搞定。
本章节作为matplotlib的入门介绍,将较为深入地挖掘几个例子,从中理解和学习matplotlib绘图的一些基本概念。
In [10]:
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
In [15]:
# -*- coding: utf-8 -*-
x = np.linspace(0, 10, 1000)
y = np.sin(x)
z = np.cos(x**2)
plt.figure(figsize=(8,4)) #调用figure创建一个Figure绘图对象,并且使它成为当前的绘图对象
plt.plot(x,y,label="$sin(x)$",color="red",linewidth=2) #调用plot函数在当前的绘图对象中进行绘图
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.savefig("test1.png") #图像的大小是576*288像素,默认dpi为72
#plt.savefig("test2.png",dpi=80)
plt.show() #显示出我们创建的所有绘图对象
matplotlib还提供了名为pylab的模块,其中包括了许多numpy和pyplot中常用的函数,方便用户快速进行计算和绘图,可以用于IPython中的快速交互式使用。
通过figsize参数可以指定绘图对象的宽度和高度,单位为英寸;dpi参数指定绘图对象的分辨率,即每英寸多少个像素,缺省值为80。因此本例中所创建的图表窗口的宽度为8*80 = 640像素。
但是用工具栏中的保存按钮保存下来的png图像的大小是800*400像素。这是因为保存图表用的函数savefig使用不同的DPI配置,savefig函数也有一个dpi参数,如果不设置的话,将使用matplotlib配置文件中的配置,此配置可以通过如下语句进行查看,关于配置文件将在后面的章节进行介绍:
In [9]:
import matplotlib
matplotlib.rcParams["savefig.dpi"]
Out[9]:
plot函数的调用方式很灵活,第一句将x,y数组传递给plot之后,用关键字参数指定各种属性:
In [50]:
x = np.arange(0, 5, 0.1)
line = plt.plot(x, x*x) # plot返回含有一个Line2D对象的列表
# 调用Line2D对象的set_*方法设置属性值
line[0].set_antialiased(False) #通过line[0]获取其第一个元素(Line2D对象)
In [51]:
# 同时绘制sin和cos两条曲线,lines是一个有两个Line2D对象的列表
lines = plt.plot(x, np.sin(x), x, np.cos(x))
# 调用setp函数同时配置多个Line2D对象的多个属性值
plt.setp(lines, color="r", linewidth=2.0)
Out[51]:
In [54]:
lines
Out[54]:
同样我们可以通过调用Line2D对象的get_*方法,或者plt.getp函数获取对象的属性值:
In [34]:
line[0].get_linewidth() #调用Line2D对象的get_*方法
Out[34]:
In [55]:
plt.getp(lines[0], "color") #plt.getp函数获取对象的属性值,返回color属性
Out[55]:
In [37]:
plt.getp(lines[1]) # 输出全部属性
注意getp函数只能对一个对象进行操作,它有两种用法:
matplotlib的整个图表为一个Figure对象,此对象在调用plt.figure函数时返回,我们也可以通过plt.gcf函数获取当前的绘图对象:
In [38]:
f = plt.gcf()
plt.getp(f)
Figure对象有一个axes属性,其值为AxesSubplot对象的列表,每个AxesSubplot对象代表图表中的一个子图,前面所绘制的图表只包含一个子图,当前子图也可以通过plt.gca获得
用plt.getp可以发现AxesSubplot对象有很多属性,例如它的lines属性为此子图所包括的 Line2D 对象列表:
In [52]:
alllines = plt.getp(plt.gca(), "lines")
alllines
Out[52]:
In [53]:
plt.getp(plt.gca())
import matplotlib matplotlib.matplotlib_fname()
如果你用文本编辑器打开此配置文件的话,你会发现它实际上是定义了一个字典。为了对众多的配置进行区分,关键字可以用点分开。
配置文件的读入可以使用 rc_params 函数,它返回一个配置字典:
In [61]:
matplotlib.rc_params()
Out[61]:
在matplotlib模块载入的时候会调用rc_params函数,并把得到的配置字典保存到rcParams变量中。
matplotlib将使用rcParams变量中的配置进行绘图。用户可以直接修改此字典中的配置,所做的改变会反映到此后所绘制的图中。例如下面的脚本所绘制的线将带有圆形的点标识符:
In [63]:
matplotlib.rcParams["lines.marker"] = "o"
import pylab
pylab.plot([1,2,3],[1,2,3])
pylab.show()
一个绘图对象(Figure)可以包含多个轴(axis),在matplotlib中用轴表示一个绘图区域,可以将其理解为子图。上面的第一个例子中,绘图对象只包括一个轴,因此只显示了一个轴(子图)。我们可以使用subplot函数快速绘制有多个轴的图表。subplot函数的调用形式如下:
subplot(numRows, numCols, plotNum)
subplot将整个绘图区域等分为numRows行 * numCols列个子区域,然后按照从左到右,从上到下的顺序对每个子区域进行编号,左上的子区域的编号为1。如果numRows,numCols和plotNum这三个数都小于10的话,可以把它们缩写为一个整数,例如subplot(323)和subplot(3,2,3)是相同的。subplot在plotNum指定的区域中创建一个轴对象。如果新创建的轴和之前创建的轴重叠的话,之前的轴将被删除。
下面的程序创建3行2列共6个轴,通过axisbg参数给每个轴设置不同的背景颜色。
In [56]:
for idx, color in enumerate("rgbyck"):
plt.subplot(320+idx+1, axisbg=color)
plt.show()
如果希望某个轴占据整个行或者列的话,可以如下调用subplot:
In [59]:
plt.subplot(221) # 第一行的左图
plt.subplot(222) # 第一行的右图
plt.subplot(212) # 第二整行
plt.show()
matplotlib API包含有三层:
backend_bases.FigureCanvas : 图表的绘制领域
backend_bases.Renderer : 知道如何在FigureCanvas上如何绘图
artist.Artist : 知道如何使用Renderer在FigureCanvas上绘图
FigureCanvas和Renderer需要处理底层的绘图操作。Artist则处理所有的高层结构,例如处理图表、文字和曲线等的绘制和布局。通常我们只和Artist打交道,而不需要关心底层的绘制细节。
Artists分为简单类型和容器类型两种。简单类型的Artists为标准的绘图元件,例如Line2D、 Rectangle、 Text、AxesImage 等等。而容器类型则可以包含许多简单类型的Artists,使它们组织成一个整体,例如Axis、 Axes、Figure等。
直接使用Artists创建图表的标准流程如下:
下面首先调用pyplot.figure函数创建Figure对象,然后调用Figure对象的add_axes方法在其中创建一个Axes对象,add_axes的参数是一个形如[left, bottom, width, height]的列表,这些数值分别指定所创建的Axes对象相对于fig的位置和大小,取值范围都在0到1之间:
In [70]:
fig = plt.figure()
ax = fig.add_axes([0.15, 0.1, 0.7, 0.3])
然后我们调用ax的plot方法绘图,创建一条曲线,并且返回此曲线对象(Line2D)。
In [71]:
line, = ax.plot([1,2,3],[1,2,1])
ax.lines
Out[71]:
ax.lines是一个包含ax所有曲线的列表,后续的ax.plot调用会往此列表中添加新的曲线。如果想删除某条曲线的话,直接从此列表中删除即可。
Axes对象还包括许多其它的Artists对象,例如我们可以通过调用set_xlabel设置其X轴上的标题:
In [76]:
ax.set_xlabel("time")
Out[76]:
Axes的xaxis属性是一个XAxis对象:
In [90]:
ax.xaxis
Out[90]:
XAxis的label属性是一个Text对象:
In [74]:
ax.xaxis.label
Out[74]:
In [77]:
ax.xaxis.label.get_text()
Out[77]:
In [103]:
fig = plt.figure()
fig.show()
fig.patch.set_color("g")
fig.canvas.draw()
patch的color属性通过set_color函数进行设置,属性修改之后并不会立即反映到图表的显示上,还需要调用fig.canvas.draw()函数才能够更新显示。
Artist对象的所有属性都通过相应的 get_* 和 set_* 函数进行读写,如果你想用一条语句设置多个属性的话,可以使用set函数:
In [88]:
fig.set(alpha=0.5, zorder=2)
fig.set_alpha(0.5*fig.get_alpha())
现在我们知道如何观察和修改已知的某个Artist对象的属性,接下来要解决如何找到指定的Artist对象。前面我们介绍过Artist对象有容器类型和简单类型两种,这一节让我们来详细看看容器类型的内容。
最大的Artist容器是matplotlib.figure.Figure,它包括组成图表的所有元素。图表的背景是一个Rectangle对象,用Figure.patch属性表示。当你通过调用add_subplot或者add_axes方法往图表中添加轴(子图时),这些子图都将添加到Figure.axes属性中,同时这两个方法也返回添加进axes属性的对象,注意返回值的类型有所不同,实际上AxesSubplot是Axes的子类。
In [96]:
fig = plt.figure()
ax1 = fig.add_subplot(211)
ax2 = fig.add_axes([0.1, 0.1, 0.7, 0.3])
In [99]:
fig.axes
Out[99]:
In [97]:
ax1
Out[97]:
In [98]:
ax2
Out[98]:
不建议直接对Figure.axes属性进行列表操作,而应该使用add_subplot, add_axes, delaxes等方法进行添加和删除操作。
In [101]:
fig = plt.figure()
ax = fig.add_subplot(111)
ax.patch.set_facecolor("green")
当你调用Axes的绘图方法(例如plot),它将创建一组Line2D对象,并将所有的关键字参数传递给这些Line2D对象,并将它们添加进Axes.lines属性中,最后返回所创建的Line2D对象列表:
In [104]:
x, y = np.random.rand(2, 100)
line, = ax.plot(x, y, "-", color="blue", linewidth=2)
line
Out[104]:
In [105]:
ax.lines
Out[105]:
与plot方法类似,绘制直方图的方法bar和绘制柱状统计图的方法hist将创建一个Patch对象的列表,每个元素实际上都是Patch的子类Rectangle,并且将所创建的Patch对象都添加进Axes.patches属性中:
In [107]:
ax = fig.add_subplot(111)
n, bins, rects = ax.hist(np.random.randn(1000), 50, facecolor="blue")
In [108]:
rects
Out[108]:
In [109]:
rects[0]
Out[109]:
In [110]:
ax.patches[0]
Out[110]:
一般我们不会直接对Axes.lines或者Axes.patches属性进行操作,而是调用add_line或者add_patch等方法,这些方法帮助我们完成许多属性设置工作:
In [115]:
fig = plt.figure()
ax = fig.add_subplot(111)
rect = matplotlib.patches.Rectangle((1,1), width=5, height=12)
In [116]:
print rect.get_axes() # rect的axes属性为空
In [117]:
ax.add_patch(rect) # 将rect添加进ax
Out[117]:
In [118]:
rect.get_axes() # 于是rect的axes属性就是ax
Out[118]:
In [120]:
ax.get_xlim() # ax的X轴范围为0到1,无法显示完整的rect
Out[120]:
In [121]:
ax.dataLim._get_bounds() # 数据的范围和rect的大小一致
Out[121]:
In [122]:
ax.autoscale_view() # 自动调整坐标轴范围
In [123]:
ax.get_xlim() # 于是X轴可以完整显示rect
Out[123]:
In [124]:
plt.show()
In [125]:
Out[125]:
下面列出Axes的创建Artist对象的方法:
Axes的方法 | 所创建的对象 | 添加进的列表 |
---|---|---|
annotate | Annotate | texts |
bars | Rectangle | patches |
errorbar | Line2D, Rectangle | lines,patches |
fill | Polygon | patches |
hist | Rectangle | patches |
imshow | AxesImage | images |
legend | Legend | legends |
plot | Line2D | lines |
scatter | PathCollection | Collections |
text | Text | texts |
下面以绘制散列图(scatter)为例,验证一下:
In [141]:
fig = plt.figure()
ax = fig.add_subplot(111)
t = ax.scatter([1,2,3], [4,5,6])
t # 返回值为PathCollection对象
Out[141]:
In [139]:
ax.collections # 返回的对象已经添加进了collections列表中
Out[139]:
In [142]:
t.get_sizes() # 获得Collection的点数
Out[142]:
In [23]:
plt.plot([1,2,3],[4,5,6])
axis = plt.gca().xaxis
axis.get_ticklocs() # 获得刻度的位置列表
Out[23]:
In [16]:
axis.get_ticklabels() # 获得刻度标签列表
Out[16]:
In [41]:
[x.get_text() for x in axis.get_ticklabels()] # 获得刻度的文本字符串
Out[41]:
In [18]:
axis.get_ticklines() # 获得主刻度线列表,图的上下刻度线共10条
Out[18]:
In [19]:
axis.get_ticklines(minor=True) # 获得副刻度线列表
Out[19]:
获得刻度线或者刻度标签之后,可以设置其各种属性,下面设置刻度线为绿色粗线,文本为红色并且旋转45度:
In [40]:
lines=plt.plot([1,2,3],[4,5,6])
axis = plt.gca().xaxis
for label in axis.get_ticklabels():
label.set_color("red")
label.set_rotation(45)
label.set_fontsize(16)
for line in axis.get_ticklines():
line.set_color("green")
line.set_markersize(25)
line.set_markeredgewidth(3)
plt.show()
上面的例子中,获得的副刻度线列表为空,这是因为用于计算副刻度的对象缺省为NullLocator,它不产生任何刻度线;而计算主刻度的对象为AutoLocator,它会根据当前的缩放等配置自动计算刻度的位置:
In [45]:
axis.get_minor_locator() # 计算副刻度的对象
Out[45]:
In [47]:
axis.get_major_locator() # 计算主刻度的对象
Out[47]:
我们可以使用程序为Axis对象设置不同的Locator对象,用来手工设置刻度的位置;设置Formatter对象用来控制刻度文本的显示。下面的程序设置X轴的主刻度为pi/4,副刻度为pi/20,并且主刻度上的文本以pi为单位:
In [44]:
# -*- coding: utf-8 -*-
from matplotlib.ticker import MultipleLocator, FuncFormatter
x = np.arange(0, 4*np.pi, 0.01)
y = np.sin(x)
plt.figure(figsize=(8,4))
plt.plot(x, y)
ax = plt.gca()
def pi_formatter(x, pos):
"""
比较罗嗦地将数值转换为以pi/4为单位的刻度文本
"""
m = np.round(x / (np.pi/4))
n = 4
if m%2==0: m, n = m/2, n/2
if m%2==0: m, n = m/2, n/2
if m == 0:
return "0"
if m == 1 and n == 1:
return "$\pi$"
if n == 1:
return r"$%d \pi$" % m
if m == 1:
return r"$\frac{\pi}{%d}$" % n
return r"$\frac{%d \pi}{%d}$" % (m,n)
# 设置两个坐标轴的范围
plt.ylim(-1.5,1.5)
plt.xlim(0, np.max(x))
# 设置图的底边距
plt.subplots_adjust(bottom = 0.15)
plt.grid() #开启网格
# 主刻度为pi/4
ax.xaxis.set_major_locator( MultipleLocator(np.pi/4) )
# 主刻度文本用pi_formatter函数计算
ax.xaxis.set_major_formatter( FuncFormatter( pi_formatter ) )
# 副刻度为pi/20
ax.xaxis.set_minor_locator( MultipleLocator(np.pi/20) )
# 设置刻度文本的大小
for tick in ax.xaxis.get_major_ticks():
tick.label1.set_fontsize(10)
plt.show()
关于刻度的定位和文本格式的东西都在matplotlib.ticker中定义,程序中使用到如下两个类:
MultipleLocator : 以指定值的整数倍为刻度放置刻度线
FuncFormatter : 使用指定的函数计算刻度文本,他会传递给所指定的函数两个参数:刻度值和刻度序号,程序中通过比较笨的办法计算出刻度值所对应的刻度文本
此外还有很多预定义的Locator和Formatter类,详细内容请参考相应的API文档。
Markdown中插入数学公式的方法
行内公式: $x=\frac{-b\pm\sqrt{b^2-4ac}}{2a}$
In [ ]: