Python数据科学手册(4) Matplotlib数据可视化
Matplotlib 是建立在 NumPy 数组基础上的多平台数据可视化程序库,可以画出类似 MATLAB 风格的交互式图形。
Matplotlib 最重要的特性之一就是具有良好的操作系统兼容性和图形显示底层接口兼容性(graphics backend)。Matplotlib 支持几十种图形显示接口与输出格式,这使得用户无论在哪种操作系统上都可以输出自己想要的图形格式。这种跨平台、面面俱到的特点已经成为Matplotlib 最强大的功能之一,atplotlib 也因此吸引了大量用户,进而形成了一个活跃的开发者团队,晋升为 Python 科学领域不可或缺的强大武器。
4.1 Matplotlib常用技巧
4.1.1 导入Matplotlib
import matplotlib as mpl import matplotlib.pyplot as plt
4.1.2 设置绘图样式
使用 plt.style 来选择图形的绘图风格。例如:
plt.style.use('classic')
4.1.3 如何显示图形
1. 在脚本中画图
如果在一个脚本文件中使用 Matplotlib,那么显示图形的时候必须使用 plt.show()。plt.show() 会启动一个事件循环(event loop),并找到所有当前可用的图形对象,然后打开一个或多个交互式窗口显示图形。
需要注意的是:一个 Python 会话(session)中只能使用一次 plt.show(),因此通常都把它放在脚本的最后。多个 plt.show() 命令会导致难以预料的显示异常,应该尽量避免。
x = np.linspace(0, 10, 100)
plt.plot(x, np.sin(x))
plt.plot(x, np.cos(x))
plt.show()
2. 在IPython shell中画图
在 IPython shell 中交互式地使用 Matplotlib 画图非常方便,在IPython 启动 Matplotlib 模式就可以使用它。为了启用这个模式,需要在启动 ipython 后使用 %matplotlib 魔法命令:
此后的任何 plt 命令都会自动打开一个图形窗口,增加新的命令,图形就会更新。
有一些变化(例如改变已经画好的线条属性)不会自动及时更新;对于这些变化,可以使用plt.draw() 强制更新。在 IPython shell 中启动 Matplotlib 模式之后,就不需要使用 plt.show() 了。
4.1.4 将图形保存为文件
使用 savefig() 命令可以将图形保存为文件。
x = np.linspace(0, 10, 100) fig = plt.figure() plt.plot(x, np.sin(x)) plt.plot(x, np.cos(x)) fig.savefig('my_figure.png')
以上代码将图形以png格式保存在目录下。
通过 canvas 对象的方法可以查看系统支持的文件格式。
fig.canvas.get_supported_filetypes()
{'ps': 'Postscript', 'eps': 'Encapsulated Postscript', 'pdf': 'Portable Document Format', 'pgf': 'PGF code for LaTeX', 'png': 'Portable Network Graphics', 'raw': 'Raw RGBA bitmap', 'rgba': 'Raw RGBA bitmap', 'svg': 'Scalable Vector Graphics', 'svgz': 'Scalable Vector Graphics', 'jpg': 'Joint Photographic Experts Group', 'jpeg': 'Joint Photographic Experts Group', 'tif': 'Tagged Image File Format', 'tiff': 'Tagged Image File Format'}
4.2 两种画图接口
Matplotlib 有一个容易让人混淆的特性,就是它的两种画图接口:一个是便捷的MATLAB 风格接口,另一个是功能更强大的面向对象接口。
4.2.1 MATLAB风格接口
Matplotlib 最初作为 MATLAB 用户的 Python 替 代 品, 许 多 语 法 都 和 MATLAB 类 似。MATLAB 风格的工具位于 pyplot(plt)接口中。下面是典型的 Matlab 风格的代码。
plt.figure() # 创建图形 # 创建两个子图中的第一个,设置坐标轴 plt.subplot(2, 1, 1) # (行、列、子图编号) plt.plot(x, np.sin(x)) # 创建两个子图中的第二个,设置坐标轴 plt.subplot(2, 1, 2) plt.plot(x, np.cos(x))
这种接口最重要的特性是有状态的(stateful):它会持续跟踪“当前的”图形和坐标轴,所有 plt 命令都可以应用。使用 plt.gcf()(获取当前图形)和 plt.gca()(获取当前坐标轴)可以查看具体信息。
4.2.2 面向对象接口
# 先创建图形网格 # ax是一个包含两个Axes对象的数组 fig, ax = plt.subplots(2) # 在每个对象上调用plot()方法 ax[0].plot(x, np.sin(x)) ax[1].plot(x, np.cos(x))
4.3 简易线形图
plt.style.use('seaborn-whitegrid') # 创建一个图形 fig 和一个坐标轴 ax fig = plt.figure() ax = plt.axes()
在 Matplotlib 里面,figure(plt.Figure 类的一个实例)可以被看成是一个能够容纳各种坐标轴、图形、文字和标签的容器。axes(plt.Axes 类的一个实例)是一个带有刻度和标签的矩形,最终会包含所有可视化的图形元素。
创建好坐标轴之后,就可以用 ax.plot 画图了。如果想在一张图中创建多条线,可以重复调用 plot 命令。
4.3.1 调整图形:线条的颜色与风格
plt.plot() 函数可以通过相应的参数设置颜色与风格。要修改颜色,就可以使用 color 参数,它支持各种颜色值的字符串。
x= np.linspace(0, 10, 100) plt.plot(x, np.sin(x - 0), color='blue') # 标准颜色名称 plt.plot(x, np.sin(x - 1), color='g') # 缩写颜色代码(rgbcmyk) plt.plot(x, np.sin(x - 2), color='0.75') # 范围在0~1的灰度值 plt.plot(x, np.sin(x - 3), color='#FFDD44') # 十六进制(RRGGBB,00~FF) plt.plot(x, np.sin(x - 4), color=(1.0,0.2,0.3)) # RGB元组,范围在0~1 plt.plot(x, np.sin(x - 5), color='chartreuse'); # HTML颜色名称
如果不指定颜色,Matplotlib 就会为多条线自动循环使用一组默认的颜色。
与之类似,你也可以用 linestyle 调整线条的风格。
plt.plot(x, x + 0, linestyle='solid') plt.plot(x, x + 1, linestyle='dashed') plt.plot(x, x + 2, linestyle='dashdot') plt.plot(x, x + 3, linestyle='dotted'); # 你可以用下面的简写形式 plt.plot(x, x + 4, linestyle='-') # 实线 plt.plot(x, x + 5, linestyle='--') # 虚线 plt.plot(x, x + 6, linestyle='-.') # 点划线 plt.plot(x, x + 7, linestyle=':'); # 实点线
一种更简洁的方式是将 linestyle 和 color 编码组合起来。
plt.plot(x, x + 0, '-g') # 绿色实线 plt.plot(x, x + 1, '--c') # 青色虚线 plt.plot(x, x + 2, '-.k') # 黑色点划线 plt.plot(x, x + 3, ':r'); # 红色实点线
4.3.2 调整图形:坐标轴上下限
调整坐标轴上下限最基础的方法是 plt.xlim() 和 plt.ylim()。
plt.plot(x, np.sin(x)) plt.xlim(-1, 11) plt.ylim(-1.5, 1.5);
还有一个方法是 plt.axis()(注意不要搞混 axes 和 axis)。通过传入 [xmin, xmax, ymin, ymax] 对应的值。
plt.plot(x, np.sin(x))
plt.axis([-1, 11, -1.5, 1.5])
- plt.axis('tight'):按照图形的内容自动收紧坐标轴,不留空白区域;
- plt.axis('equal'):让屏幕上显示的图形 x 轴单位长度与 y 轴单位长度相等。
4.3.3 设置图形标签
plt.plot(x, np.sin(x)) plt.title("A Sine Curve") plt.xlabel("x") plt.ylabel("sin(x)")
Matplotlib 内置了一个简单快速的方法,可以用来创建图例,即plt.legend()。在 plt.plot 函数中用 label 参数为每条线设置一个标签更加简单。
plt.plot(x, np.sin(x), '-g', label='sin(x)') plt.plot(x, np.cos(x), ':b', label='cos(x)') plt.legend()
一些 MATLAB 风格的方法和面向对象方法的转换如下所示:
- plt.xlabel() → ax.set_xlabel()
- plt.ylabel() → ax.set_ylabel()
- plt.xlim() → ax.set_xlim()
- plt.ylim() → ax.set_ylim()
- plt.title() → ax.set_title()
在用面向对象接口画图时,不需要单独调用这些函数,通常采用 ax.set() 方法一次性设置所有的属性是更简便的方法。
ax = plt.axes() ax.plot(x, np.sin(x)) ax.set(xlim=(0, 10), ylim=(-2, 2), \ xlabel='x', ylabel='sin(x)', \ title='A Simple Plot')
4.4 简易散点图
4.4.1 用plt.plot画散点图
x = np.linspace(0, 10, 30) y = np.sin(x) plt.plot(x, y, 'o', color='black')
部分图形标记展示如下:
rng = np.random.RandomState(0) for marker in ['o', '.', ',', 'x', '+', 'v', '^', '<', '>', 's', 'd']: plt.plot(rng.rand(5), rng.rand(5), marker, \ label="marker='{0}'".format(marker)) plt.legend(numpoints=1) plt.xlim(0, 1.8)
plt.plot(x, y, '-or'); # 直线(-)、圆圈(o)、红色(r)
这些标记还可以与线条、颜色代码组合起来,画出一条连接散点的线。
plt.plot支持许多设置线条和散点属性的参数,例如:
plt.plot(x, y, '-p', color='b', \ markersize=15, linewidth=4, \ markerfacecolor='w', \ markeredgecolor='r', \ markeredgewidth=2)
4.4.2 用plt.scatter画散点图
plt.scatter 与 plt.plot 的主要差别在于,前者在创建散点图时具有更高的灵活性,可以单独控制每个散点与数据匹配,也可以让每个散点具有不同的属性(大小、表面颜色、边框颜色等)。
rng = np.random.RandomState(0) x = rng.randn(100) y = rng.randn(100) colors = rng.rand(100) sizes = 300 * rng.rand(100) plt.scatter(x, y, c=colors, s=sizes, \ alpha=0.3, cmap='viridis') plt.colorbar() # 显示颜色条
可以用 Scikit-Learn 程序库里面的鸢尾花(iris)数据来演示。
from sklearn.datasets import load_iris iris = load_iris() features = iris.data.T plt.scatter(features[0], features[1], alpha=0.3, \ s=100*features[3], c=iris.target, cmap='viridis') plt.xlabel(iris.feature_names[0]) plt.ylabel(iris.feature_names[1])
每个点的坐标值 (x, y) 分别表示花萼的长度和宽度,而点的大小表示花瓣的宽度,三种颜色对应三种不同类型的鸢尾花。
看到熟悉的iris忍不住和ggplot2对比一下,ggplot作图如下:
library(ggplot2) ggplot(data=iris,aes(Sepal.Length,Sepal.Width, size=Petal.Width, colour=Species,alpha=I(0.3)))+ geom_point()+guides(colour=F,size=F)+ labs(x="sepal length(cm)",y="sepal width(cm)")+ theme_light()+theme(axis.title.x= element_text(size = 10), axis.title.y= element_text(size = 10))
iris.feature_names
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
iris.data.shape
(150, 4)
iris.data是150行4列的数据矩阵,T表示转置。
plt.plot 与 plt.scatter 在数据量较小的时候,效率上的差异不大。但是当数据变大到几千个散点时,plt.plot 的效率将大大高于 plt.scatter。这是由于 plt.scatter 会对每个散点进行单独的大小与颜色的渲染,因此渲染器会消耗更多的资源。
4.5 可视化异常处理
4.5.1 基本误差线
x = np.linspace(0, 10, 50) dy = 0.8 y = np.sin(x) + dy * np.random.randn(50) plt.errorbar(x, y, yerr=dy, fmt='.k')
plt.errorbar(x, y, yerr=dy, fmt='o', color='black', \ ecolor='lightgray', elinewidth=3, capsize=0)
4.5.2 连续误差
Scikit-Learn 程序库 API 里面有一个简单的高斯过程回归方法(Gaussian process regression,GPR),这是用一种非常灵活的非参数方程(nonparametric function)对带有不确定性的连续测量值进行拟合的方法。
可以通过区域填充表示连续误差。
from sklearn.gaussian_process import GaussianProcessRegressor from sklearn.gaussian_process.kernels import ConstantKernel, RBF # 定义模型和要画的数据 kernel = ConstantKernel(constant_value=0.2, \ constant_value_bounds=(1e-4, 1e4)) * RBF(length_scale=0.5, \ length_scale_bounds=(1e-4, 1e4)) gpr = GaussianProcessRegressor(kernel=kernel, n_restarts_optimizer=2) model = lambda x: x * np.sin(x) xdata = np.array([1, 3, 5, 6, 8]).reshape(-1, 1) ydata = model(xdata) # 计算高斯过程拟合结果 gpr.fit(xdata, ydata) xfit = np.linspace(0, 10, 1000).reshape(-1, 1) mu, cov = gpr.predict(xfit, return_cov=True) yfit = mu.ravel() dyfit = 1.96 * np.sqrt(np.diag(cov))# 95%置信区间 # 将结果可视化 xfit = xfit.ravel() plt.plot(xdata, ydata, 'or') plt.plot(xfit, yfit, '-', color='gray') plt.fill_between(xfit.ravel(), yfit - dyfit, yfit + dyfit, \ color='gray', alpha=0.2) plt.xlim(0, 10)
从结果图形中可以非常直观地看出高斯过程回归方法拟合的效果:在接近样本点的区域,模型受到很强的约束,拟合误差非常小,非常接近真实值;而在远离样本点的区域,模型不受约束,误差不断增大。
4.6 密度图与等高线图
用 plt.contour 画等高线图、用 plt.contourf 画带有填充色的等高线图(filled contour plot)的色彩、用 plt.imshow 显示图形。
def f(x, y): return np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x) x = np.linspace(0, 5, 50) y = np.linspace(0, 5, 40) X, Y = np.meshgrid(x, y) Z = f(X, Y) plt.contour(X, Y, Z, colors='black')
当图形中只使用一种颜色时,默认使用虚线表示负数,使用实线表示正数。
plt.contour(X, Y, Z, 20, cmap='Set1')
在 IPython 里使用 plt.cm.<TAB> 浏览 Matplotlib 中的配色方案。
使用 plt.imshow()可以将二维数组渲染成渐变图。
plt.imshow(Z, extent=[0, 5, 0, 5], origin='lower', \ cmap='Set1') plt.colorbar() plt.axis(aspect='image')
还有一个可能会用到的方法,就是将等高线图与彩色图组合起来。首先需要用一幅背景色半透明的彩色图,与另一幅坐标轴相同、带数据标签的等高线图叠放在一起。
contours = plt.contour(X, Y, Z, 3, colors='black') plt.clabel(contours, inline=True, fontsize=8) plt.imshow(Z, extent=[0, 5, 0, 5], origin='lower', \ cmap='Set1', alpha=0.5) plt.colorbar()