第4章 Matplotlib数据可视化
Matplotlib 是建立在 NumPy 数组基础上的多平台数据可视化程序库,最初被设计用于完善 SciPy 的生态环境。John Hunter 在 2002 年提出了Matplotlib 的构思——希望通过一个 IPython 的补丁,让 IPython 命令行可以用 gnuplot 画出类似 MATLAB 风格的交互式图形。
Matplotlib 最重要的特性之一就是具有良好的操作系统兼容性(跨平台)和图形显示底层接口兼容性(graphics backend)。Matplotlib 支持几十种图形显示接口与输出格式,这使得用户无论在哪种操作系统上都可以输出自己想要的图形格式。
然而近几年,Matplotlib 的界面与风格似乎有点跟不上时代。新的画图工具,如 R 语言中的 ggplot 和 ggvis,都开始使用 D3js 和 HTML5canvas 构建的网页可视化工具。相比之下,Matplotlib 更显沧桑。目前,新版的 Matplotlib 已经可以轻松实现主流的绘图风格,人们不断在 Matplotlib 的基础上开发出新的程序包,实现更加简洁、现代化的 API,例如 Seaborn、ggplot、HoloViews、Altair,以及 Pandas 对 Matplotlib 的 API 封装的画图功能。
虽然已经有了封装后的高级工具,但是掌握 Matplotlib 的语法更能让你灵活地控制最终的图形结果。因此,即使新工具的出现说明社区正在逐渐放弃直接使用底层的 Matplotlib API 画图的做法,但我依然觉得 Matplotlib 是数据可视化技术中不可或缺的一环。
4.1 Matplotlib 常用技巧
# 1、导入 Matplotlib
import matplotlib as mpl
import matplotlib.pyplot as plt
# 2、设置绘图格式
plt.style.use('classic')
# 3.1、在脚本中画图
# show():启动一个事件循环,并找到所有当前可用的图形对象,并打开一个或多个交互式窗口显示图形
x = np.linspace(0, 10, 100)
plt.plot(x, np.sin(x))
plt.plot(x, np.cos(x))
plt.show()
# 3.2、在 IPython Shell 中画图
# 此后的任何 plt 命令都会自动打开一个图形窗口,增加新的命令,图形就会更新
# 有一些变化(例如改变已经画好的线条属性)不会自动及时更新,可以使用 plt.draw() 强制更新
%matplotlib
# 3.3、在 IPython Notebook 中画图
# 运行命令之后,就会直接将 PNG 格式图形文件嵌入在单元中
# 在 Notebook 中启动静态图形
%matplotlib inline
# 在 Notebook 中启动交互式图形
%matplotlib notebook
# 4、将图形保存为各种不同的数据格式
fig.savefig('my_figure.png')
4.2 两种画图接口
# 1、MATLAB 风格接口:是有状态的,会持续跟踪“当前的”图标和坐标轴,所有 plt 命令都可以应用
# 创建图形
plt.figure()
# 创建两个子图中的第一个,设置坐标轴(行、列、子图编号)
plt.subplot(2, 1, 1)
plt.plot(x, np.sin(x))
# 创建两个子图中的第二个,设置坐标轴(行、列、子图编号)
plt.subplot(2, 1, 2)
plt.plot(x, np.cos(x))
# 获取当前图形
plt.gcf()
# 获取当前坐标轴
plt.gca()
# 2、面向对象接口:适应更复杂的场景,不再受到当前“活动”图形或坐标轴的限制,而变成了显式的 Figure 和 Axes 的方法
# 先创建图形网格,ax 是一个包含两个 Axes 对象的数组
fig, ax = plt.subplots(2)
# 在每个对象上调用 plot() 方法
ax[0].plot(x, np.sin(x))
ax[1].plot(x, np.cos(x))
4.3 简易线形图
%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
import numpy as np
# 创建图形(容器)与坐标轴
fig = plt.figure()
ax = plt.axes()
# 绘制正弦曲线
x = np.linespace(0, 10, 1000)
ax.plot(x, np.sin(x)) # 写法1
plt.plot(x, np.sin(x)) # 写法2
# 在一张图中创建多条线
plt.plot(x, np.sin(x))
plt.plot(x, np.cos(x))
# 调整线条颜色:可以使用标准颜色名称、缩写颜色代码、0-1 灰度值、十六进制、RGB 元组、HTML 颜色名称
plt.plot(x, np.sin(x-0), color='blue') # 标准颜色名称
# 调整线条风格:可以使用风格名称(solid/dashed/dashdot/dotted)或简写(-/--/-./:)
plt.plot(x, x+0, linestyle='solid')
# 同时调整线条颜色与风格
plt.plot(x, x+0, '-g') # 绿色实线
plt.plot(x, x+1, '--c') # 青色虚线
plt.plot(x, x+2, '-.k') # 黑色点划线
plt.plot(x, x+3, ':r') # 红色实点线
# 调整图形:坐标轴上下限
plt.plot(x, np.sin(x))
plt.xlim(-1, 11) # 调整x轴
plt.ylim(-1.5, 1.5) # 调整y轴
plt.xlim(10, 0) # 调整x轴为逆序显示
plt.axis([-1, 11, -1.5, 1.5]) # 同时调整 x 轴和 y 轴限值
plt.axis('tight') # 根据图形自动收紧坐标轴,不留空白区域
plt.axis('equal') # x轴与y轴单位长度相等
# 设置图形标签
plt.plot(x, np.sin(x))
plt.title("A Sine Curve") # 图形标题
plt.xlabel("x") # x轴标题
plt.ylabel("sin(x)") # y轴标题
# 创建图例:为每条线设置一个标签
plt.plot(x, np.sin(x), '-g', label='sin(x)')
plt.plot(x, np.cos(x), ':b', label='cos(x)')
plt.legend() # 将每条线的标签与其风格、颜色自动匹配
# 面向对象接口:使用 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')
# 两种接口转换关系:
# 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()
4.4 简易散点图
%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
import numpy as np
x = np.linspace(0, 10, 30) # 在 [0 ,10] 之间返回 30 个点
y = np.sin(x)
# 1、使用 plt.plot 画散点图
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, '-ok') # 直线(-)、圆圈(o)、黑色(k)
plt.plot(x, y, '-p', color='gray',
linewidth=4,
markersize=15,
markerfacecolor='white',
markeredgecolor='gray',
markeredgewidth=2)
# 2、使用 plt.scatter 画散点图
plt.scatter(x, y, marker='o')
# 在创建散点图时具有更高的灵活性,可以单独控制每个散点与数据匹配,也可以让每个散点具有不同的属性
# 对每个散点进行单独的大小与颜色的渲染,渲染器会消耗更多的资源
rng = np.random.RandomState(0) # 伪随机数生成器,可以得到同一个随机数组
x = rng.randn(100)
y = rng.randn(100)
colors = rng.rand(100)
sizes = 1000 * rng.rand(100)
# 散点的颜色和大小一一对应
plt.scatter(x, y, c=colors, s=sizes, alpha=0.3, cmap='viridis')
# 显示颜色条
plt.colorbar()
# 例:鸢尾花(iris)数据
# 每个点的坐标值 (x, y) 分别表示花萼的长度和宽度,而点的大小表示花瓣的宽度,三种颜色对应三种不同类型的鸢尾花
from sklearn.datasets import load_iris
iris = load_iris()
features = iris.data.T
plt.scatter(features[0], features[1], alpha=0.2, s=100*features[3], c=iris.target, cmap='viridis')
plt.xlabel(iris.feature_names[0])
plt.ylabel(iris.feature_names[1])
4.5 可视化异常处理
在数据可视化的结果中用图形将误差有效地显示出来,就可以提供更充分的信息。
%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
import numpy as np
# 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)
# 2、连续误差
from sklearn.gaussian_process import GaussianProcess
# 定义模型和要画的数据
model = lambda x: x * np.sin(x)
xdata = np.array([1, 3, 5, 6, 8])
ydata = model(xdata)
# 计算高斯过程拟合结果
gp = GaussianProcess(corr='cubic', theta0=1e-2, thetaL=1e-4, thetaU=1E-1, random_start=100)
gp.fit(xdata[:, np.newaxis], ydata)
xfit = np.linspace(0, 10, 1000)
yfit, MSE = gp.predict(xfit[:, np.newaxis], eval_MSE=True)
dyfit = 2 * np.sqrt(MSE) # 2*sigma~95% 置信区间
# 绘制连续误差线
plt.plot(xdata, ydata, 'or')
plt.plot(xfit, yfit, '-', color='gray')
# 通过区域填充表示连续误差
plt.fill_between(xfit, yfit - dyfit, yfit + dyfit, color='gray', alpha=0.2)
plt.xlim(0, 10)
# 从图中可以看出高斯过程回归方法拟合的效果:
# 在接近样本点的区域,模型受到很强的约束,拟合误差非常小,非常接近真实值
# 而在远离样本点的区域,模型不受约束,误差不断增大
4.6 密度图与等高线图
%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-white')
import numpy as np
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)
# 1、绘制线形等高线图
# 当图形中只使用一种颜色时,默认使用虚线表示负数,使用实线表示正数
plt.contour(X, Y, Z, colors='black')
# 调整配色方案:让更多的线条显示不同的颜色
# 将数据范围等分为 20 份,然后用不同的颜色表示
plt.contour(X, Y, Z, 20, cmap='RdGy')
# 填充等高线图(线条之间的间隙比较大时)
plt.contourf(X, Y, Z, 20, cmap='RdGy')
plt.colorbar() # 显示颜色条
# 渲染成渐变图
plt.imshow(Z, extent=[0, 5, 0, 5], origin='lower', cmap='RdGy')
plt.colorbar()
plt.axis(aspect='image')
# 结合使用:在彩色图上加上带数据标签的等高线
# 用一幅背景色半透明的彩色图(可以通过alpha参数设置透明度),与另一幅坐标轴相同、带数据标签的等高线图叠放在一起
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='RdGy', alpha=0.5)
plt.colorbar()
4.7 频次直方图、数据区间划分和分布密度
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('seaborn-white')
data = np.random.randn(1000)
# 1、绘制频次直方图
plt.hist(data)
# 调整直方图显示参数
plt.hist(data, bins=30, normed=True, alpha=0.5, histtype='stepfilled', color='steelblue', edgecolor='none')
# 同坐标轴的多个频次直方图
x1 = np.random.normal(0, 0.8, 1000)
x2 = np.random.normal(-2, 1, 1000)
x3 = np.random.normal(3, 2, 1000)
kwargs = dict(histtype='stepfilled', alpha=0.3, normed=True, bins=40)
plt.hist(x1, **kwargs)
plt.hist(x2, **kwargs)
plt.hist(x3, **kwargs)
# 计算频次直方图(就是计算每段区间的样本数),而并不想画图显示它们
counts, bin_edges = np.histogram(data, bins=5)
# [ 12 190 468 301 29]
# 2、绘制二维频次直方图(将二维数组按照二维区间进行切分)
# 用一个多元高斯分布生成 x 轴与 y 轴的样本数据
mean = [0, 0]
cov = [[1, 1], [1, 2]]
x, y = np.random.multivariate_normal(mean, cov, 10000).T
# 2.1、简单方法
plt.hist2d(x, y, bins=30, cmap='Blues')
cb = plt.colorbar()
cb.set_label('counts in bin')
# 计算二维频次直方图,而不是画图
counts, xedges, yedges = np.histogram2d(x, y, bins=30)
# 2.2、六边形区间划分(将二维数据集分割成蜂窝状)
plt.hexbin(x, y, gridsize=30, cmap='Blues')
cb = plt.colorbar(label='count in bin')
# 2.3、核密度估计
from scipy.stats import gaussian_kde
# 拟合数组维度 [Ndim, Nsamples]
data = np.vstack([x, y])
kde = gaussian_kde(data)
# 用一对规则的网格数据进行拟合
xgrid = np.linspace(-3.5, 3.5, 40)
ygrid = np.linspace(-6, 6, 40)
Xgrid, Ygrid = np.meshgrid(xgrid, ygrid)
Z = kde.evaluate(np.vstack([Xgrid.ravel(), Ygrid.ravel()]))
# 画出结果图
plt.imshow(Z.reshape(Xgrid.shape), origin='lower', aspect='auto', extent=[-3.5, 3.5, -6, 6], cmap='Blues')
cb = plt.colorbar()
cb.set_label("density")
4.8 配置图例
想在可视化图形中使用图例,可以为不同的图形元素分配标签。图例会默认显示所有元素的标签(忽略那些不带标签的元素),可以指定显示图例中的哪些元素和标签。
%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('classic')
import numpy as np
x = np.linspace(0, 10, 1000)
fig, ax = plt.subplots()
ax.plot(x, np.sin(x), '-b', label='Sine')
ax.plot(x, np.cos(x), '--r', label='Cosine')
ax.axis('equal')
# 1、创建图例
leg = ax.legend()
# 设置图例的位置,并取消外边框,设置图例的标签列数
ax.legend(loc='upper left', frameon=False, ncol=2)
# 为图例定义圆角边框(fancybox)、增加阴影、改变外边框透明度(framealpha值),或者改变文字间距
ax.legend(fancybox=True, framealpha=1, shadow=True, borderpad=1)
# plt.plot() 命令可以一次创建多条线,返回线条实例列表
# 方法1:将需要显示的线条传入 plt.legend()
y = np.sin(x[:, np.newaxis] + np.pi * np.arange(0, 2, 0.5))
lines = plt.plot(x, y)
# lines 变量是一组 plt.Line2D 实例
plt.legend(lines[:2], ['first', 'second'])
# 方法2:只为需要在图例中显示的线条设置标签
plt.plot(x, y[:, 0], label='first')
plt.plot(x, y[:, 1], label='second')
plt.plot(x, y[:, 2:])
plt.legend(framealpha=1, frameon=True)
# 2、同时显示多个图例
fig, ax = plt.subplots()
lines = []
styles = ['-', '--', '-.', ':']
x = np.linspace(0, 10, 1000)
for i in range(4):
lines += ax.plot(x, np.sin(x - i * np.pi / 2), styles[i], color='black')
ax.axis('equal')
# 设置第一个图例要显示的线条和标签
ax.legend(lines[:2], ['line A', 'line B'], loc='upper right', frameon=False)
# 创建第二个图例,通过 add_artist 方法添加到图上
from matplotlib.legend import Legend
leg = Legend(ax, lines[2:], ['line C', 'line D'], loc='lower right', frameon=False)
ax.add_artist(leg)
# 3、例:用点的尺寸来表明美国加州不同城市的人口数量
import pandas as pd
cities = pd.read_csv('data/california_cities.csv')
# 提取感兴趣的数据
lat, lon = cities['latd'], cities['longd']
population, area = cities['population_total'], cities['area_total_km2']
# 用不同尺寸和颜色的散点图表示数据,但是不带标签
plt.scatter(lon, lat, label=None, c=np.log10(population), cmap='viridis', s=area, linewidth=0, alpha=0.5)
plt.axis(aspect='equal')
plt.xlabel('longitude')
plt.ylabel('latitude')
plt.colorbar(label='log$_{10}$(population)') plt.clim(3, 7)
# 下面创建一个图例:
# 画一些带标签和尺寸的空列表
for area in [100, 300, 500]:
plt.scatter([], [], c='k', alpha=0.3, s=area, label=str(area) + ' km$^2$')
plt.legend(scatterpoints=1, frameon=False, labelspacing=1, title='City Area')
plt.title('California Cities: Area and Population')
4.9 配置颜色条图例
图例通过离散的标签表示离散的图形元素。然而,对于图形中由彩色的点、线、面构成的连续标签,用颜色条来表示的效果比较好。在 Matplotlib 里面,颜色条是一个独立的坐标轴,可以指明图形中颜色的含义。
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('classic')
x = np.linspace(0, 10, 1000)
I = np.sin(x) * np.cos(x[:, np.newaxis])
# 1、创建颜色条图例
plt.imshow(I)
plt.colorbar()
# 设置颜色
plt.imshow(I, cmap='gray')
# 配色方案:
# 顺序配色方案:一组连续的颜色构成的配色方案(例如 binary 或 viridis)
# 互逆配色方案:通常由两种互补的颜色构成,表示正反两种含义(例如 RdBu 或 PuOr)
# 定性配色方案:随机顺序的一组颜色(例如 rainbow 或 jet)
plt.imshow(I, cmap='RdBu')
plt.colorbar(extend='both')
# 颜色条刻度限制
plt.clim(-1, 1)
# 离散型颜色条:传入配色方案和需要的区间数量
plt.imshow(I, cmap=plt.cm.get_cmap('Blues', 6))
# 2、例:手写数字
# 加载数字 0~5 的图形,对其进行可视化
from sklearn.datasets import load_digits
digits = load_digits(n_class=6)
fig, ax = plt.subplots(8, 8, figsize=(6, 6))
for i, axi in enumerate(ax.flat):
axi.imshow(digits.images[i], cmap='binary')
axi.set(xticks=[], yticks=[])
# 用IsoMap方法将数字投影到二维空间
from sklearn.manifold import Isomap
iso = Isomap(n_components=2)
projection = iso.fit_transform(digits.data)
# 画图
plt.scatter(projection[:, 0], projection[:, 1], lw=0.1, c=digits.target, cmap=plt.cm.get_cmap('cubehelix', 6))
plt.colorbar(ticks=range(6), label='digit value')
plt.clim(-0.5, 5.5)
# 从图中可以看出:
# 数字 5 与数字 3 在投影中有大面积重叠,说明一些手写的 5 与 3 难以区分,因此自动分类算法也更容易搞混它们
# 其他的数字,像数字 0 与数字 1,隔得特别远,说明两者不太可能出现混淆
4.10 多子图
有时候需要从多个角度对数据进行对比。Matplotlib 为此提出了子图(subplot)的概念:在较大的图形中同时放置一组较小的坐标轴。这些子图可能是画中画(inset)、网格图(grid of plots),或者是其他更复杂的布局形式。
# 1、手动创建子图
# 默认坐标轴
ax1 = plt.axes()
# 设置坐标轴的底坐标、左坐标、宽度、高度,数值的取值范围是左下角(原点)为 0,右上角为 1
ax2 = plt.axes([0.65, 0.65, 0.2, 0.2])
# 或者通过面向对象接口创建子图
fig = plt.figure()
ax1 = fig.add_axes([0.1, 0.5, 0.8, 0.4], xticklabels=[], ylim=(-1.2, 1.2))
ax2 = fig.add_axes([0.1, 0.1, 0.8, 0.4], ylim=(-1.2, 1.2))
x = np.linspace(0, 10)
ax1.plot(np.sin(x))
ax2.plot(np.cos(x))
# 2、简易网格子图:若干彼此对齐的行列子图
# 从 1 开始
for i in range(1, 7):
plt.subplot(2, 3, i)
plt.text(0.5, 0.5, str((2, 3, i)), fontsize=18, ha='center')
# 或者通过面向对象接口创建子图
fig = plt.figure()
# 设置子图宽高间距
fig.subplots_adjust(hspace=0.4, wspace=0.4)
for i in range(1, 7):
ax = fig.add_subplot(2, 3, i)
ax.text(0.5, 0.5, str((2, 3, i)), fontsize=18, ha='center')
# 3、用一行代码创建多个子图,并返回一个包含子图的 NumPy 数组(共享x轴与y轴坐标)
# 从 0 开始
fig, ax = plt.subplots(2, 3, sharex='col', sharey='row')
# 坐标轴存放在一个 NumPy 数组中,按照 [row, col] 取值
for i in range(2):
for j in range(3):
ax[i, j].text(0.5, 0.5, str((i, j)), fontsize=18, ha='center')
# 4、实现更复杂的排列方式:不规则的多行多列子图网格
# plt.GridSpec() 对象本身不能直接创建一个图形,它只是 plt.subplot() 命令可以识别的简易接口
# 创建一个带行列间距的 2×3 网格
grid = plt.GridSpec(2, 3, wspace=0.4, hspace=0.3)
# 通过类似 Python 切片的语法设置子图的位置和扩展尺寸
plt.subplot(grid[0, 0])
plt.subplot(grid[0, 1:])
plt.subplot(grid[1, :2])
plt.subplot(grid[1, 2])
# 绘制多轴频次直方图
# 创建一些正态分布数据
mean = [0, 0]
cov = [[1, 1], [1, 2]]
x, y = np.random.multivariate_normal(mean, cov, 3000).T
# 设置坐标轴和网格配置方式
fig = plt.figure(figsize=(6, 6))
grid = plt.GridSpec(4, 4, hspace=0.2, wspace=0.2)
main_ax = fig.add_subplot(grid[:-1, 1:])
y_hist = fig.add_subplot(grid[:-1, 0], xticklabels=[], sharey=main_ax)
x_hist = fig.add_subplot(grid[-1, 1:], yticklabels=[], sharex=main_ax)
# 主坐标轴画散点图
main_ax.plot(x, y, 'ok', markersize=3, alpha=0.2)
# 次坐标轴画频次直方图
# 在散点图下方绘制 x 轴直方图
x_hist.hist(x, 40, histtype='stepfilled', orientation='vertical', color='gray')
x_hist.invert_yaxis()
# 在散点图左方绘制 y 轴直方图
y_hist.hist(y, 40, histtype='stepfilled', orientation='horizontal', color='gray')
y_hist.invert_xaxis()
4.11 文字与注释
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.style.use('seaborn-whitegrid')
import numpy as np
import pandas as pd
# 例:节假日对美国出生率的影响
births = pd.read_csv('births.csv')
quartiles = np.percentile(births['births'], [25, 50, 75])
mu, sig = quartiles[1], 0.74 * (quartiles[2] - quartiles[0])
births = births.query('(births > @mu - 5 * @sig) & (births < @mu + 5 * @sig)')
births['day'] = births['day'].astype(int)
births.index = pd.to_datetime(10000 * births.year + 100 * births.month + births.day, format='%Y%m%d')
births_by_date = births.pivot_table('births', [births.index.month, births.index.day])
births_by_date.index = [pd.datetime(2012, month, day) for (month, day) in births_by_date.index]
fig, ax = plt.subplots(figsize=(12, 4))
births_by_date.plot(ax=ax)
# 1、在图上增加文字标签
style = dict(size=10, color='gray')
ax.text('2012-1-1', 3950, "New Year's Day", **style)
ax.text('2012-7-4', 4250, "Independence Day", ha='center', **style)
ax.text('2012-9-4', 4850, "Labor Day", ha='center', **style)
ax.text('2012-10-31', 4600, "Halloween", ha='right', **style)
ax.text('2012-11-25', 4450, "Thanksgiving", ha='center', **style)
ax.text('2012-12-25', 3850, "Christmas ", ha='right', **style)
# 设置坐标轴标题
ax.set(title='USA births by day of year (1969-1988)', ylabel='average daily births')
# 设置 x 轴刻度值,让月份居中显示
ax.xaxis.set_major_locator(mpl.dates.MonthLocator())
ax.xaxis.set_minor_locator(mpl.dates.MonthLocator(bymonthday=15))
ax.xaxis.set_major_formatter(plt.NullFormatter())
ax.xaxis.set_minor_formatter(mpl.dates.DateFormatter('%h'))
# 2、坐标变换与文字位置
# ax.transData:以数据为基准的坐标变换(受坐标轴上下线影响)
# ax.transAxes:以坐标轴为基准的坐标变换(以坐标轴维度为单位)
# fig.transFigure:以图形为基准的坐标变换(以图形维度为单位)
fig, ax = plt.subplots(facecolor='lightgray')
ax.axis([0, 10, 0, 10])
# 虽然 transform=ax.transData 是默认值,但还是设置一下
ax.text(1, 5, ". Data: (1, 5)", transform=ax.transData) # 坐标
ax.text(0.5, 0.1, ". Axes: (0.5, 0.1)", transform=ax.transAxes) # 坐标轴比例
ax.text(0.2, 0.2, ". Figure: (0.2, 0.2)", transform=fig.transFigure) # 图形比例
# 3、箭头与注释
%matplotlib inline
fig, ax = plt.subplots()
x = np.linspace(0, 20, 1000)
ax.plot(x, np.cos(x))
x.axis('equal')
# xy 指定箭头指向坐标,xytext 指定文本坐标
ax.annotate('local maximum', xy=(6.28, 1), xytext=(10, 4), arrowprops=dict(facecolor='black', shrink=0.05))
# 还可以通过 arrowprops 参数指定箭头样式和弧度
ax.annotate('local minimum', xy=(5 * np.pi, -1), xytext=(2, -6),
arrowprops=dict(arrowstyle="->",
connectionstyle="angle3, angleA=0, angleB=-90"))
4.12 自定义坐标轴刻度
%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-whitegrid')
import numpy as np
# 1、主要刻度与次要刻度
ax = plt.axes(xscale='log', yscale='log')
ax.plot(np.random.rand(50))
# 可以通过设置每个坐标轴的formatter与locator对象,自定义这些刻度属性(包括刻度线的位置和标签)
# 查看主要刻度和次要刻度
ax.xaxis.get_major_locator() # LogLocator
ax.xaxis.get_minor_locator() # LogLocator
# 查看主要标签与次要标签
ax.xaxis.get_major_formatter() # LogFormatterMathtext
ax.xaxis.get_minor_formatter() # NullFormatter 不显示
# 2、隐藏刻度与标签
# 隐藏 y 轴刻度和标签
ax.yaxis.set_major_locator(plt.NullLocator())
# 隐藏 x 轴标签
ax.xaxis.set_major_formatter(plt.NullFormatter())
# 例:展示人脸图片
fig, ax = plt.subplots(5, 5, figsize=(5, 5))
fig.subplots_adjust(hspace=0, wspace=0)
# 从scikit-learn获取一些人脸照片数据
from sklearn.datasets import fetch_olivetti_faces
faces = fetch_olivetti_faces().images
# 隐藏人脸图形的坐标轴
for i in range(5):
for j in range(5):
ax[i, j].xaxis.set_major_locator(plt.NullLocator())
ax[i, j].yaxis.set_major_locator(plt.NullLocator())
ax[i, j].imshow(faces[10 * i + j], cmap="bone")
# 3、增减刻度数量:调整刻度太过拥挤或者稀疏
# 为每个坐标轴设置主要刻度定位器(最多刻度数量)
for axi in ax.flat:
axi.xaxis.set_major_locator(plt.MaxNLocator(3))
axi.yaxis.set_major_locator(plt.MaxNLocator(3))
# 4、花哨的刻度格式
ax.xaxis.set_major_locator(plt.MultipleLocator(np.pi / 2))
ax.xaxis.set_minor_locator(plt.MultipleLocator(np.pi / 4))
# 自定义设置函数
def format_func(value, tick_number): # 定义省略
ax.xaxis.set_major_formatter(plt.FuncFormatter(format_func))
4.13 Matplotlib 自定义:配置文件与样式表
# 1、例:美化频次直方图
import matplotlib.pyplot as plt
plt.style.use('classic')
import numpy as np
%matplotlib inline
# 默认直方图:
x = np.random.randn(1000)
plt.hist(x)
# 调整直方图:
# 用灰色背景
ax = plt.axes(axisbg='#E6E6E6')
ax.set_axisbelow(True)
# 画上白色的网格线
plt.grid(color='w', linestyle='solid')
# 隐藏坐标轴的线条
for spine in ax.spines.values():
spine.set_visible(False)
# 隐藏上边与右边的刻度
ax.xaxis.tick_bottom()
ax.yaxis.tick_left()
# 弱化刻度与标签
ax.tick_params(colors='gray', direction='out')
for tick in ax.get_xticklabels():
tick.set_color('gray')
for tick in ax.get_yticklabels():
tick.set_color('gray')
# 设置频次直方图轮廓色与填充色
ax.hist(x, edgecolor='#E6E6E6', color='#EE6666')
# 2、修改默认配置:rcParams(包含所有图形元素的默认风格)
# 这些设置会保存在 .matplotlibrc 文件中
from matplotlib import cycler
colors = cycler('color', ['#EE6666', '#3388BB', '#9988DD', '#EECC55', '#88BB44', '#FFBBBB'])
plt.rc('axes', facecolor='#E6E6E6', edgecolor='none', axisbelow=True, grid=True, prop_cycle=colors) plt.rc('grid', color='w', linestyle='solid')
plt.rc('xtick', direction='out', color='gray')
plt.rc('ytick', direction='out', color='gray')
plt.rc('patch', edgecolor='#E6E6E6')
plt.rc('lines', linewidth=2)
# 生成图形时不需要另外设置样式了
plt.hist(x)
# 3、样式表
# 这些样式会保存在 .mplstyle 文件中
# 查看所有可用的样式
plt.style.available
# 使用某种样式表(会改变后面所有的样式)
plt.style.use('stylename')
# 临时更换样式
with plt.style.context('stylename'):
make_a_plot()
4.14 用Matplotlib画三维图
# 导入 3D 子模块
from mpl_toolkits import mplot3d
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
# 1、三维数据点与线:以三角螺旋线为例
ax = plt.axes(projection='3d')
# 三维线的数据
zline = np.linspace(0, 15, 1000)
xline = np.sin(zline)
yline = np.cos(zline)
ax.plot3D(xline, yline, zline, 'gray')
# 三维散点的数据
zdata = 15 * np.random.random(100)
xdata = np.sin(zdata) + 0.1 * np.random.randn(100)
ydata = np.cos(zdata) + 0.1 * np.random.randn(100)
ax.scatter3D(xdata, ydata, zdata, c=zdata, cmap='Greens')
# 2、三维等高线图
def f(x, y):
return np.sin(np.sqrt(x ** 2 + y ** 2))
x = np.linspace(-6, 6, 30)
y = np.linspace(-6, 6, 30)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.contour3D(X, Y, Z, 50, cmap='binary')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')
# 调整角度:可以通过代码调整,也可以在交互界面通过拖拽进行调整
# 俯仰角(x-y 平面的旋转角度)
# 方位角(绕 z 轴顺时针旋转角度)
ax.view_init(60, 35)
# 3、线框图和曲面图
# 线框图
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.plot_wireframe(X, Y, Z, color='black')
ax.set_title('wireframe')
# 曲面图
ax = plt.axes(projection='3d')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='viridis', edgecolor='none')
ax.set_title('surface')
# 画曲面图需要二维数据,但可以不是直角坐标系(也可以用极坐标)
r = np.linspace(0, 6, 20)
theta = np.linspace(-0.9 * np.pi, 0.8 * np.pi, 40)
r, theta = np.meshgrid(r, theta)
X = r * np.sin(theta)
Y = r * np.cos(theta)
Z = f(X, Y)
ax = plt.axes(projection='3d')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='viridis', edgecolor='none')
# 4、曲面三角剖分:上述要求均匀采样的网格数据显得太过严格且不太容易实现,可以使用三角剖分图形
theta = 2 * np.pi * np.random.random(1000)
r = 6 * np.random.random(1000)
x = np.ravel(r * np.sin(theta))
y = np.ravel(r * np.cos(theta))
z = f(x, y)
# 先为数据点创建一个散点图,对将要采样的图形有一个基本认识
ax = plt.axes(projection='3d')
ax.scatter(x, y, z, c=z, cmap='viridis', linewidth=0.5)
# 还有许多地方需要修补
# 首先找到一组所有点都连接起来的三角形,然后用这些三角形创建曲面
ax = plt.axes(projection='3d')
ax.plot_trisurf(x, y, z, cmap='viridis', edgecolor='none')
# 例:莫比乌斯带:把一根纸条扭转 180 度后,再把两头粘起来做成的纸带圈
# 从拓扑学的角度看,莫比乌斯带非常神奇,它总共只有一个面
theta = np.linspace(0, 2 * np.pi, 30) # 弧度
w = np.linspace(-0.25, 0.25, 8) # 宽度
w, theta = np.meshgrid(w, theta)
phi = 0.5 * theta
# x - y 平面内的半径
r = 1 + w * np.cos(phi)
x = np.ravel(r * np.cos(theta))
y = np.ravel(r * np.sin(theta))
z = np.ravel(w * np.sin(phi))
# 用基本参数化方法定义三角剖分
from matplotlib.tri import Triangulation
tri = Triangulation(np.ravel(w), np.ravel(theta))
ax = plt.axes(projection='3d')
ax.plot_trisurf(x, y, z, triangles=tri.triangles, cmap='viridis', linewidths=0.2)
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
ax.set_zlim(-1, 1)
4.15 用 Basemap 可视化地理数据
地理数据可视化是数据科学中一种十分常见的可视化类型。Matplotlib做此类可视化的主要工具是 Basemap 工具箱,它是 Matplotlib 的 mpl_toolkits 命名空间里的众多工具箱之一。坦白说,Basemap 用起来有点笨重,就算做点儿简单的可视化图也需要花费比预期更长的时间。在处理比较复杂的地图可视化任务时,更现代的解决方案可能会更适用一些,比如 leaflet 开发库或 Google Maps API。然而,Basemap 符合 Python 用户的使用习惯。
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
# 绘制地图
plt.figure(figsize=(8, 8))
m = Basemap(projection='lcc', resolution=None, width=8E6, height=8E6, lat_0=45, lon_0=-100,)
m.etopo(scale=0.5, alpha=0.5) # ETOPO 地图
# 地图上的(经度, 纬度)对应图上的(x, y)坐标
# 标出西雅图的位置
x, y = m(-122.3, 47.6)
plt.plot(x, y, 'ok', markersize=5)
plt.text(x, y, ' Seattle', fontsize=12)
# 1、地图投影
# 像地球这样的球体,可以通过球面透视法将三维球面投影成一个二维平面,不会造成变形,也不会破坏其连续性
from itertools import chain
def draw_map(m, scale=0.2):
# 绘制地貌晕渲图
m.shadedrelief(scale=scale)
# 绘制经纬线:用字典表示经纬度
lats = m.drawparallels(np.linspace(-90, 90, 13))
lons = m.drawmeridians(np.linspace(-180, 180, 13))
# 字典的键是 plt.Line2D 示例
lat_lines = chain(*(tup[1][0] for tup in lats.items()))
lon_lines = chain(*(tup[1][0] for tup in lons.items()))
all_lines = chain(lat_lines, lon_lines)
# 用循环将所有线设置成需要的样式
for line in all_lines:
line.set(linestyle='-', alpha=0.3, color='w')
# 圆柱投影:最简单的地图投影类型,纬度线与经度线分别映射成水平线与竖直线
fig = plt.figure(figsize=(8, 6), edgecolor='w')
m = Basemap(projection='cyl', # 等距圆柱投影
resolution=None,
llcrnrlat=-90, # 左下角纬度
urcrnrlat=90, # 右上角纬度
llcrnrlon=-180, # 左下角经度
urcrnrlon=180) # 右上角经度
draw_map(m)
# 伪圆柱投影、透视投影、圆锥投影略
# 2、绘制地图背景
# 绘制海岸线
fig, ax = plt.subplots(1, 2, figsize=(12, 8))
for i, res in enumerate(['l', 'h']):
m = Basemap(projection='gnom', lat_0=57.3, lon_0=-6.2, width=90000, height=120000, resolution=res, ax=ax[i])
m.fillcontinents(color="#FFDDCC", lake_color='#DDEEFF')
m.drawmapboundary(fill_color="#DDEEFF")
m.drawcoastlines()
ax[i].set_title("resolution='{0}'".format(res))
# 例:美国加州城市数据
import pandas as pd
cities = pd.read_csv('data/california_cities.csv')
# 提取需要的数据
lat = cities['latd'].values
lon = cities['longd'].values
population = cities['population_total'].values
area = cities['area_total_km2'].values
# 1.绘制地图背景
fig = plt.figure(figsize=(8, 8))
m = Basemap(projection='lcc', resolution='h', lat_0=37.5, lon_0=-119, width=1E6, height=1.2E6)
m.shadedrelief()
m.drawcoastlines(color='gray')
m.drawcountries(color='gray')
m.drawstates(color='gray')
# 2.绘制城市数据散点,用颜色表示人口数据 # and size reflecting area
m.scatter(lon, lat, latlon=True, c=np.log10(population), s=area, cmap='Reds', alpha=0.5)
# 3.创建颜色条与图例
plt.colorbar(label=r'$\log_{10}({\rm population})$')
plt.clim(3, 7)
# 用虚拟点绘制图例
for a in [100, 300, 500]:
plt.scatter([], [], c='k', alpha=0.5, s=a, label=str(a) + ' km$^2$')
plt.legend(scatterpoints=1, frameon=False, labelspacing=1, loc='lower left')
# 例:地表温度数据
# !curl -O http://data.giss.nasa.gov/pub/gistemp/gistemp250.nc.gz
# !gunzip gistemp250.nc.gz
# 读取数据
from netCDF4 import Dataset
data = Dataset('gistemp250.nc')
# 选择指定日期数据
from netCDF4 import date2index
from datetime import datetime
timeindex = date2index(datetime(2014, 1, 15), data.variables['time'])
# 加载经度与纬度数据,并将气温也提取出来
lat = data.variables['lat'][:]
lon = data.variables['lon'][:]
lon, lat = np.meshgrid(lon, lat)
temp_anomaly = data.variables['tempanomaly'][timeindex]
# 绘制数据的彩色网格
# 在图中浅浅地绘制了海岸线作为参照
fig = plt.figure(figsize=(10, 8))
m = Basemap(projection=’lcc’, resolution=’c’, width=8E6, height=8E6, lat_0=45, lon_0=-100,)
m.shadedrelief(scale=0.5)
m.pcolormesh(lon, lat, temp_anomaly, latlon=True, cmap=’RdBu_r’)
plt.clim(-8, 8)
m.drawcoastlines(color=’lightgray’)
plt.title(‘January 2014 Temperature Anomaly’)
plt.colorbar(label=’temperature anomaly (度 C)’)
4.16 用 Seaborn 做数据可视化
Seaborn 在 Matplotlib 的基础上开发了一套 API,为默认的图形样式和颜色设置提供了理智的选择,为常用的统计图形定义了许多简单的高级函数,并与 Pandas DataFrame 的功能有机结合。
# 0、Matplotlib 样式与 Seaborn 样式对比
import matplotlib.pyplot as plt
plt.style.use('classic')
%matplotlib inline
import numpy as np
import pandas as pd
# 创建一些数据
rng = np.random.RandomState(0)
x = np.linspace(0, 10, 500)
y = np.cumsum(rng.randn(500, 6), 0)
# 用 Matplotlib 默认样式画图
plt.plot(x, y)
plt.legend('ABCDEF', ncol=2, loc='upper left')
# Seaborn 样式
import seaborn as sns
sns.set()
# 同样的画图代码!
plt.plot(x, y)
plt.legend('ABCDEF', ncol=2, loc='upper left')
# 1、Seaborn 绘制频次直方图、KDE 和密度图
# 频次直方图和多变量的联合分布图(写法与 Matplotlib 一样)
data = np.random.multivariate_normal([0, 0], [[5, 2], [2, 2]], size=2000)
data = pd.DataFrame(data, columns=['x', 'y'])
for col in 'xy':
plt.hist(data[col], normed=True, alpha=0.5)
# KDE 获取变量分布的平滑估计:Seaborn 通过 sns.kdeplot 实现
for col in 'xy':
sns.kdeplot(data[col], shade=True)
# 用 distplot 可以让频次直方图与 KDE 结合起来
sns.distplot(data['x'])
sns.distplot(data['y'])
# 如果向 kdeplot 输入的是二维数据集,那么就可以获得一个二维数据可视化图
sns.kdeplot(data)
# 用 sns.jointplot 可以同时看到两个变量的联合分布与单变量的独立分布
with sns.axes_style('white'):
sns.jointplot("x", "y", data, kind='kde')
# 可以向 jointplot 函数传递一些参数。例如,可以用六边形块代替频次直方图
with sns.axes_style('white'):
sns.jointplot("x", "y", data, kind='hex')
# 2、矩阵图
# 需要对多维数据集进行可视化时,最终都要使用矩阵图
# 如果想画出所有变量中任意两个变量之间的图形,用矩阵图探索多维数据不同维度间的相关性非常有效
# 使用鸢尾花数据集来演示,其中有三种鸢尾花的花瓣与花萼数据
iris = sns.load_dataset("iris")
iris.head()
# 可视化样本中多个维度的关系非常简单,直接用 sns.pairplot 即可
sns.pairplot(iris, hue='species', size=2.5)
# 3、分面频次直方图
# 有时观察数据最好的方法就是借助数据子集的频次直方图
# 来看看某个餐厅统计的服务员收取小费的数据
tips = sns.load_dataset('tips')
tips.head()
tips['tip_pct'] = 100 * tips['tip'] / tips['total_bill']
grid = sns.FacetGrid(tips, row="sex", col="time", margin_titles=True)
grid.map(plt.hist, "tip_pct", bins=np.linspace(0, 40, 15))
# 4、因子图
# 可以通过它观察一个参数在另一个参数间隔中的分布情况
with sns.axes_style(style='ticks'):
g = sns.factorplot("day", "total_bill", "sex", data=tips, kind="box")
g.set_axis_labels("Day", "Total Bill")
# 5、联合分布
# 绘制不同数据集的联合分布和各数据本身的分布
with sns.axes_style('white'):
sns.jointplot("total_bill", "tip", data=tips, kind='hex')
# 联合分布图也可以自动进行 KDE 和回归
# 带回归拟合的联合分布
sns.jointplot("total_bill", "tip", data=tips, kind='reg')
# 6、条形图
# 时间序列数据可以用 sns.factorplot 画出条形图
# 使用行星数据进行演示
planets = sns.load_dataset('planets')
planets.head()
with sns.axes_style('white'):
g = sns.factorplot("year", data=planets, aspect=2, kind="count", color='steelblue')
# 还可以对比用不同方法(method参数)发现行星的数量
with sns.axes_style('white'):
g = sns.factorplot("year", data=planets, aspect=4.0, kind='count', hue='method', order=range(2001, 2015))
g.set_ylabels('Number of Planets Discovered')
# 例:探索马拉松比赛成绩数据
# !curl -O https://raw.githubusercontent.com/jakevdp/marathon-data/
# master/marathon-data.csv
# 把字符串转换成时间类型的函数
def convert_time(s):
h, m, s = map(int, s.split(':'))
return pd.datetools.timedelta(hours=h, minutes=m, seconds=s)
# 加载数据并完成时间转换
data = pd.read_csv('marathon-data.csv', converters={'split':convert_time, 'final':convert_time})
# 将时间换算成秒
data['split_sec'] = data['split'].astype(int) / 1E9
data['final_sec'] = data['final'].astype(int) / 1E9
# 数据初览:通过 jointplot 函数画图
# 马拉松前半程成绩与全程成绩的对比
with sns.axes_style('white'):
g = sns.jointplot("split_sec", "final_sec", data, kind='hex')
g.ax_joint.plot(np.linspace(4000, 16000), np.linspace(8000, 32000), ':k')
# 图中的实点线表示一个人全程保持一个速度跑完马拉松,即上半程与下半程耗时相同
# 然而实际的成绩分布表明,绝大多数人都是越往后跑得越慢
# 如果前后半程差异系数(split difference)小于 0,就表示这个人是后半程加速型选手
# 绘制差异系数的分布图,0 表示前后半程耗时相同
sns.distplot(data['split_frac'], kde=False)
plt.axvline(0, color="k", linestyle="--")
# 后半程加速的选手数量
sum(data.split_frac < 0)
# 再来看看前后半程差异系数与其他变量有没有相关性
# 用一个矩阵图pairgrid画出所有变量间的相关性
g = sns.PairGrid(data, vars=['age', 'split_sec', 'final_sec', 'split_frac'], hue='gender', palette='RdBu_r')
g.map(plt.scatter, alpha=0.8)
g.add_legend()
# 从图中可以看出,虽然前后半程差异系数与年龄没有显著的相关性,但是与比赛的最终成绩有显著的相关性
# 全程耗时最短的选手,往往都是在前后半程尽量保持节奏一致、耗时非常接近的人
# 来看男女选手前后半程差异系数的频次直方图
sns.kdeplot(data.split_frac[data.gender=='M'], label='men', shade=True)
sns.kdeplot(data.split_frac[data.gender=='W'], label='women', shade=True)
plt.xlabel('split_frac')
# 有趣的是,在前后半程耗时接近的选手中,男选手比女选手要多很多
# 男女选手的分布看起来几乎都是双峰分布
# 将男女选手不同年龄(age)的分布函数画出来,用小提琴图(violin plot)进行这两种分布的对比
sns.violinplot("gender", "split_frac", data=data, palette=["lightblue", "lightpink"])
# 对比两个由年龄构成函数的小提琴图。在数组中创建一个新列,表示每名选手的年龄段
data['age_dec'] = data.age.map(lambda age: 10 * (age // 10))
men = (data.gender == 'M')
women = (data.gender == 'W')
# 用小提琴图表示不同性别、年龄段的前后半程差异系数
with sns.axes_style(style=None):
sns.violinplot("age_dec", "split_frac",
hue="gender", data=data,
split=True, inner="quartile",
palette=["lightblue", "lightpink"])
# 可以看出男女选手的分布差异:20 多岁至 50 多岁各年龄段的男选手的前后半程差异系数概率密度都比同年龄段的女选手低一些(或者可以说任意年龄都如此)
# 所有八十岁以上的女选手都比同年龄段的男选手的表现好。这可能是由于这个年龄段的选手寥寥无几,样本太少。
# 再看看后半程加速型选手的数据:他们都是谁?前后半程差异系数与比赛成绩正相关吗?
# 用 regplot 为数据自动拟合一个线性回归模型
# 男女选手的前后半程差异系数与比赛成绩
g = sns.lmplot('final_sec', 'split_frac', col='gender', data=data, markers=".", scatter_kws=dict(color='c'))
g.map(plt.axhline, y=0.1, color="k", ls=":")
# 似乎有显著后半程加速的选手都是比赛成绩在 15 000 秒,即 4 小时之内的种子选手。
# 低于这个成绩的选手很少有显著的后半程加速。
4.17 参考资料
Matplotlib 的在线文档
Matplotlib 画廊
InteractiveApplications Using Matplotlib
其他 Python 画图程序库:
- Bokeh
一个用 Python 做前端的 JavaScript 可视化程序库,支持非常强大的交互可视化功能,可以处理非常大的批数据和 / 或流数据。Python 前端会生成一份 JSON数据结构,通过 Bokeh 的 JS 引擎进行渲染。
- Plotly
Plotly 公司开发的同名开源产品,其设计理念与 Bokeh 类似。由于 Plotly 从一开始就是主打产品,因此得到了高水平的开发支持。可以免费使用。
- Vispy
一个侧重于大数据动态可视化的项目。由于它建立在 OpenGL 接口上并且可以充分利用电脑的显卡,因此可以渲染出令人叹为观止的大型数据可视化图。
- Vega
与 Vega-Lite 采用声明式(declarative)图形表示方法,是在数据可视化基础语言多年的研究成果上形成的产品。最终图形渲染是 JavaScript,但是 API 与编程语言无关。这就是用 Altair 程序包 实现的 Python API。虽然目前还不成熟,但我依然因这款产品也许可以为 Python 和其他编程语言提供相同的数据可视化基础理念而兴奋不已。
Python 社区里的数据可视化空间可谓日新月异,很可能我现在写的这些内容在刚刚出版时就已经过时了。请及时关注 Python 数据可视化的最新进展!