將多個 boxplot 畫在同一張

Python Sep 1, 2022

(group boxplot) put several boxplots sharing the same x in the same figure

使用 matplotlib 的 boxplot ,將不同種但有相同分析指標的畫在一起,方便對比同一指標下不同種的差別。直接使用 boxplot 是沒辦法將不同資料組合在一起,但可以藉由指定位置跟寬度,來達成所需,最後可以畫出類似於下圖或封面圖的效果。除此之外,也會順帶介紹一些 boxplot 相關參數。

boxplot 參數介紹

上圖是將我們後面用到的相關參數表現出來:

  • 左側是調整 boxplot 的各個部分 (flier, cap, whisker, box, media) 的影響範圍,並由 *props 所設置(如 flierprops, capprops 等)
  • 下方 xtick 預設會是從 1 至 num of cols,每一個 column 產生一個 box
  • width 預設是 0.5 且是整個 box 的寬度
  • position 是中心點且預設為 xtick 上。

將多個 boxplot 畫在一起

雖然 matplotlib 並不直接讓我們能夠結合多個 boxplot,但藉由設定 width, position 可以將各個 box 排好,再藉由顏色來區分。

假如要將 n 個類別 (num_kind) 放在同一指標裡頭,用預設的 base position (1, 2, 3, ..., n)來畫的話,各個的間距是 1,避免兩邊衝突到,我們所畫的範圍 total width 這裡限縮為 0.9 。

那每一類別分配到的空間是 \( \frac{total\ width}{n} \),為避免各類別靠太近,就只用 \( \frac{total\ width}{n+1} \) 來畫 box (綠色),那他們之間的空間加總起來就也會有 \( \frac{total\ width}{n+1} \);但下一個的中心點距離上一個中心點還是 \( \frac{total\ width}{n} \) (藍色)。

初始位置的計算可以用 base position (紫色) 先扣除 total width 的一半,移到最左邊 (黃色),再加上\( \frac{total\ width}{n} \) 的一半,來移到第一個中心點 (紅色)。配合前面所講的距離,就可以依序算出第 k 類別 (k = 0, 1, ..., num_kind - 1)的中心點位置

\[ position_k = (base\_position - \frac{total\ width}{2} + \frac{total\ width}{2n}) + k \times \frac{total\ width}{n} \]

那當然這是我的一種算法,大家可以根據自己的喜好去調整位置以及寬度。

實作參考

引用相關函式庫

import numpy as np
import matplotlib.pyplot as plt

numpy 拿來產生資料,而 matplotlib 拿來畫 boxplot

生成資料

def generate_data(num_data, num_label, num_kind):
    # we only use Cn as color, so we limit the num_kind
    assert(num_kind < 9)
    x_list = [np.random.rand(num_data, num_label) for i in range(num_kind)]
    color_list = ['C' + str(i) for i in range(num_kind)]
    label_list = ['label' + str(i) for i in range(num_label)]
    return x_list, color_list, label_list

因為我們使用 Cn 作為顏色,所以只能用 C0 ~ C8,這裡加上檢查確保不會超出範圍,每組就生成出 \( num\_data \times num\_label \) 的資料。

畫圖

def multiple_boxplot(x_list, color_list, legend_list, label_list):
    num_kind = len(x_list)
    assert(num_kind == len(color_list))
    num_label = len(label_list)
    plt.rcParams.update({'font.size': 18})
    fig, ax = plt.subplots(1, 1, figsize=(10, 10))
    # the total width of one label (width <= diff of base_position)
    total_width = 0.9
    # the tick position i.e. the position for one boxplot
    base_position = np.arange(num_label) + 1
    # compute the beginning position for each num_label from 1 to num_label
    position = base_position - total_width/2 + total_width/2/num_kind
    # box_list stores the box color information for legend
    box_list=[]  
    for i in np.arange(num_kind):
        color = color_list[i]
        bp = ax.boxplot(x_list[i], 
                        # use n+1 not n for space between kind
                        widths=total_width/(num_kind+1),
                        positions=position + i * total_width/num_kind,
                        # patch_artist=True is necessary for the color
                        patch_artist=True,
                        boxprops=dict(facecolor=color, color=color, alpha=0.5),
                        capprops=dict(color=color),
                        whiskerprops=dict(color=color),
                        flierprops=dict(color=color, markeredgecolor=color))
                        # change the median color by adding medianprops=dict(color=c)
        # add the boxes color to the list
        box_list.append(bp["boxes"][0])
    # set the legend
    ax.legend(box_list, legend_list, framealpha=0.3)
    ax.set_xticks(base_position)
    ax.set_xticklabels(label_list)
    # use white background
    fig.patch.set_facecolor('w')
    plt.show()

如果要著色的話,記得要將 patch_artist 設為 True,才能改變顏色。前面沒提到的變數有 num_label 用來記錄有幾種指標要畫 。 我個人是習慣將 median 保留原始顏色,但如果要改也是把 medianprops 加回去,並選擇自己要的顏色即可。使用 boxplot 回傳中的 ["boxes"][0] 當作圖例使用,含有邊跟面的顏色。最後記得要指定 xticks 跟相對應的 label,如果有改位置的話這裡可能需要更動。

展示

這裡偷懶,直接把顏色當圖例的名字使用

  • 三個指標(num_label)、四種類別(num_kind)
x_list, color_list, label_list = generate_data(100, 3, 4)
multiple_boxplot(x_list, color_list, color_list, label_list)
  • 四種指標、七種類別
x_list, color_list, label_list = generate_data(100, 4, 7)
multiple_boxplot(x_list, color_list, color_list, label_list)

參考資料

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.