在一些情況中,如果能將不同的數據圖表並列展示,對於我們進行數據分析和比較會很有幫助。Matplotlib 提供了子圖表的概念來實現這一點:單個圖表中可以包括一組小的 axes 用來展示多個子圖表。這些子圖表可以是插圖,網格狀分布或其他更復雜的布局。在本節中我們會介紹 Matplotlib 中用來構建子圖表的四個函數。
import matplotlib.pyplot as plt plt.style.use('seaborn-white') import numpy as np
plt.axes
:手動構建子圖表
構建 axes 作為子圖表的最基礎方法就是使用plt.axes
函數。正如我們前面已經看到,默認情況下,這個函數夠創建一個標准的 axes 對象填滿整個圖表區域。plt.axes
函數也可以接收一個可選的列表參數用來指定在 axes 在整個圖表中的坐標點位置。列表中有四個數值分別為[left, bottom, width, height]
(取值都是 0-1),代表着子圖表的左邊、底部、寬度、高度在整個圖表中左邊、底部、寬度、高度所占的比例值。
例如,我們可以在距離左邊和底部 65%的位置,以插圖的形式放置一個寬度和高度都是 20%子圖表,上述數值應該為[0.65, 0.65, 0.2, 0.2]
:
ax1 = plt.axes() # 標准圖表 ax2 = plt.axes([0.65, 0.65, 0.2, 0.2]) #子圖表 plt.show()
與上述等價的面向對象接口的語法是fig.add_axes()
。我們使用這個方法來創建兩個垂直堆疊的子圖表:
fig = plt.figure() # 獲得figure對象 ax1 = fig.add_axes([0.1, 0.5, 0.8, 0.4], xticklabels=[], ylim=(-1.2, 1.2)) # 左邊10% 底部50% 寬80% 高40% ax2 = fig.add_axes([0.1, 0.1, 0.8, 0.4], ylim=(-1.2, 1.2)) # 左邊10% 底部10% 寬80% 高40% x = np.linspace(0, 10) ax1.plot(np.sin(x)) ax2.plot(np.cos(x));
這樣我們就有兩個子圖表(上面的子圖表沒有 x 軸刻度),這兩個子圖表正好吻合:上面圖表的底部是整個圖表高度 50%位置,而下面圖表的頂部也是整個圖表的 50%位置(0.1+0.4)。
plt.subplot
:簡單網格的子圖表
將子圖表的行與列對齊是一個很常見的需求,因此 Matplotlib 提供了一些簡單的函數來實現它們。這些函數當中最底層的是plt.subplot()
,它會在網格中創建一個子圖表。函數接受三個整數參數,網格行數,網格列數以及該網格子圖表的序號(從左上角向右下角遞增):
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')
plt.subplots_adjust
函數用來調整這些子圖表之間的距離。下面的代碼使用了與plt.subplot()
等價的面向對象接口方法fig.add_subplot()
:
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')
上例中我們指定了plt.subplots_adjust
函數的hspace
和wspace
參數,它們代表這沿着高度和寬度方向子圖表之間的距離,單位是子圖表的大小(在本例中,距離是子圖表寬度和高度的 40%)。
plt.subplots
:一句代碼設置所有網格子圖表
上面的方法當我們需要創建大量的子圖表網格時會變得非常冗長乏味,特別是如果我們需要將內部圖表 x 軸和 y 軸標簽隱藏的情況下。因此,plt.subplots
在這種情況下是一個合適的工具(注意末尾有個 s)。這個函數會一次性創建所有的網格子圖表,而不是單個網格,並將它們在一個 NumPy 數組中返回。參數是行數和列數,還有兩個可選的關鍵字參數sharex
和sharey
,可以讓你指定不同子圖表之間的關聯。
下面我們來創建一個 網格的子圖表,其中每一行的子圖表共享它們的 y 軸,而每一列的子圖表共享它們的 x 軸:
fig, ax = plt.subplots(2, 3, sharex='col', sharey='row')
注意上面我們設置了sharex
和sharey
之后,內部子圖表的 x 軸和 y 軸的標簽就自動被去掉了。返回值中 ax 是一個 NumPy 數組,里面含有每一個子圖表的實例,你可以使用 NumPy 索引的語法很簡單的獲得它們:
# axes是一個2×3的數組,可以通過[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') fig
並且相對於plt.subplot
,plt.subplots()
更復合 Python 從 0 開始進行索引的習慣。
plt.GridSpec
:更復雜的排列
當你需要子圖表在網格中占據多行或多列時,plt.GridSpec()
正是你所需要的。plt.GridSpec()
對象並不自己創建圖表;它只是一個可以被傳遞給plt.subplot()
的參數。例如,一個兩行三列並帶有指定的寬度高度間隔的 gridspec 可以如下創建:
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 # 使用GridSpec創建網格並加入子圖表 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軸和y軸方向繪制直方圖 x_hist.hist(x, 40, histtype='stepfilled', orientation='vertical', color='gray') x_hist.invert_yaxis() # x軸方向(右下)直方圖倒轉y軸方向 y_hist.hist(y, 40, histtype='stepfilled', orientation='horizontal', color='gray') y_hist.invert_xaxis() # y軸方向(左上)直方圖倒轉x軸方向 plt.show()
這種沿着數據各自方向分布並繪制相應圖表的需求是很通用的,因此在 Seaborn 包中它們有專門的 API 來實現