1. 程式人生 > 實用技巧 >Matplotlib基礎--多個子圖表

Matplotlib基礎--多個子圖表

在一些情況中,如果能將不同的資料圖表並列展示,對於我們進行資料分析和比較會很有幫助。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函式的hspacewspace引數,它們代表這沿著高度和寬度方向子圖表之間的距離,單位是子圖表的大小(在本例中,距離是子圖表寬度和高度的 40%)。

plt.subplots:一句程式碼設定所有網格子圖表

上面的方法當我們需要建立大量的子圖表網格時會變得非常冗長乏味,特別是如果我們需要將內部圖表 x 軸和 y 軸標籤隱藏的情況下。因此,plt.subplots在這種情況下是一個合適的工具(注意末尾有個 s)。這個函式會一次性建立所有的網格子圖表,而不是單個網格,並將它們在一個 NumPy 陣列中返回。引數是行數和列數,還有兩個可選的關鍵字引數sharexsharey,可以讓你指定不同子圖表之間的關聯。

下面我們來建立一個網格的子圖表,其中每一行的子圖表共享它們的 y 軸,而每一列的子圖表共享它們的 x 軸:

fig, ax = plt.subplots(2, 3, sharex='col', sharey='row')

注意上面我們設定了sharexsharey之後,內部子圖表的 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.subplotplt.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 來實現