HOWTO · Seaborn

Seaborn 箱線圖

本教程演示瞭如何在 python 中使用 seaborn 模組建立箱線圖

本頁內容

使用 seaborn.boxplot() 摘要呈現數值分布,或比較不同類別的數值。明確指定資料欄位,讓 Seaborn 知道各變數應對應到哪個座標軸。箱形圖會顯示中位數與四分位距(IQR);使用預設鬚線規則時,鬚線會延伸至距離箱體不超過 1.5 倍 IQR 的最遠觀測值,距離更遠的觀測值則會以個別點呈現。

帶註釋的箱線圖示意圖,標出了圖的各個部分:箱體從下四分位數延伸到上四分位數,內部有中位數線,鬚線到達下限和上限極值,鬚線之外的值顯示為單個離群點。

本文的所有範例都使用相同的組成部分:箱體涵蓋資料的中間一半,內部的線是中位數,鬚線依所選規則標記範圍,鬚線之外的點會個別繪製。

建立單一分布的箱形圖

將測量值放在 DataFrame 的一個欄位中,並將該欄位指定給 y。這樣能清楚表明資料的用途,也能讓圖表有合適的座標軸標籤。下方範例使用固定數值,因此每次執行時輸入都相同。

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

data = pd.DataFrame({"Score": [12, 14, 15, 16, 18, 19, 21, 24]})
ax = sns.boxplot(data=data, y="Score")
ax.set_title("Score distribution")
plt.show()

8 個 Score 值的 Seaborn 箱線圖:一個垂直箱體,內部有中位數線,鬚線到達最低和最高分數。

箱體內的線代表中位數。箱體從第一四分位數延伸至第三四分位數,因此箱體高度代表觀測值中間的一半。箱體越長,表示資料中央部分的分散程度越大。鬚線則依所選規則顯示數值範圍,不一定會延伸到最小值和最大值。

比較不同類別的分布

將類別指定給 x,並將數值測量欄位指定給 y,即可在每個類別下各自顯示一個箱體。在這組資料中,每個箱體都摘要呈現一個 Quantity 水準的 Price。加入 hue="Product" 會將各類別再依產品分組,方便比較相同數量下的不同產品。

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

data = pd.DataFrame(
    {
        "Quantity": [5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8],
        "Price": [9, 10, 15, 16, 13, 14, 15, 18, 11, 12, 14, 15, 16, 17, 18, 19],
        "Product": ["A", "A", "A", "A", "B", "B", "B", "B"] * 2,
    }
)
ax = sns.boxplot(data=data, x="Quantity", y="Price", hue="Product")
ax.set_title("Price by quantity and product")
plt.show()

依 Quantity 等級 5 到 8、依 Product A 和 Product B 拆分的 Price 的 Seaborn 箱線圖;在每個數量等級上,Product B 的箱體都高於 Product A 的箱體。

當額外分組有助於回答比較問題時,可使用 hue。如果分組太多,箱體可能會擠在一起;此時可簡化類別,或改用多張圖,讓各組分布仍然清楚易讀。

使用 Strip Plot 顯示個別觀測值

箱形圖會將資料濃縮為摘要統計量。如果讀者也需要看到每筆觀測值,可疊加 seaborn.stripplot()。兩層圖使用相同的座標軸和資料對應方式,再加入少量水平抖動,避免數值相近的點完全重疊。

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

data = pd.DataFrame({"Score": [12, 14, 15, 16, 18, 19, 21, 24]})
ax = sns.boxplot(data=data, y="Score", color="lightgray")
sns.stripplot(data=data, y="Score", color="darkred", jitter=0.15, ax=ax)
ax.set_title("Scores with individual observations")
plt.show()

以淺灰色繪製的 Score 箱線圖,上面疊加了深紅色的 strip plot,使 8 個觀測值中的每一個都顯示為箱體內部或旁邊帶抖動的點。

箱體仍代表摘要,而每個標記則代表資料中的一列。資料集較大時,點可能互相重疊;可調整標記大小或透明度,讓密集區域仍易於辨識。

自訂箱形圖

使用 Matplotlib 的座標軸範圍方法聚焦於相關數值區間,使用 linewidth 調整箱體與鬚線邊框的粗細,並使用 palette 為類別指定顏色。orient 參數可控制圖表方向;例如,當資料對應方式相容時,orient="h" 會要求繪製水平圖。座標軸範圍只會改變顯示視野,因此設定時請謹慎選擇範圍並維持清楚的刻度,避免隱藏重要觀測值。

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

data = pd.DataFrame(
    {
        "Quantity": [5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8],
        "Price": [9, 10, 15, 16, 13, 14, 15, 18, 11, 12, 14, 15, 16, 17, 18, 19],
        "Product": ["A", "A", "A", "A", "B", "B", "B", "B"] * 2,
    }
)
ax = sns.boxplot(
    data=data,
    x="Quantity",
    y="Price",
    hue="Product",
    linewidth=2.5,
    palette="Set2",
)
ax.set_ylim(8, 20)
ax.set_title("Customized price comparison")
plt.show()

客製化後的價格比較:箱體和鬚線邊框更粗,Product A 和 Product B 使用 Set2 調色盤顏色,y 軸限制在 8 到 20 的範圍內。

調色盤的顏色會對應到 hue 分組。若要使用水平箱體,請交換類別與測量值的對應方式,並設定 orient="h";此時應使用 set_xlim() 而非 set_ylim() 設定座標軸範圍。

使用 catplot() 建立箱形圖

若要使用圖層級介面,可搭配 kind="box" 使用 catplot()。它接受相同的明確資料對應方式,也能在需要依另一個變數拆分比較時,透過 col 或 row 建立不同面板。單張圖可使用座標軸層級的 seaborn.boxplot();若圖表需要分面,則可使用 catplot()。

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

data = pd.DataFrame(
    {
        "Quantity": [5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8],
        "Price": [9, 10, 15, 16, 13, 14, 15, 18, 11, 12, 14, 15, 16, 17, 18, 19],
    }
)
grid = sns.catplot(data=data, x="Quantity", y="Price", kind="box")
grid.set_axis_labels("Quantity", "Price")
grid.fig.suptitle("Price by quantity")
plt.show()

kind 設定為 box 的圖形級 catplot:在單一面板中為 Quantity 等級 5 到 8 各繪製一個 Price 箱體,上方有圖形標題。

由於 catplot() 建構的是整個圖形,它回傳的是網格物件而不是單一座標軸;應像範例中那樣在網格上設定標籤和標題,而不是呼叫 ax.set_title()。

處理欄位缺失錯誤並設定鬚線範圍

由於每個對應都以名稱指定 DataFrame 的欄位,拼寫錯誤的名稱會在繪製任何內容之前失敗。以下呼叫請求了資料中不存在的欄位 Scroe:

import pandas as pd
import seaborn as sns

data = pd.DataFrame({"Score": [12, 14, 15, 16, 18, 19, 21, 24]})
ax = sns.boxplot(data=data, y="Scroe")

該呼叫以下列錯誤結束:

ValueError: Could not interpret value `Scroe` for `y`. An entry with this name does not appear in `data`.

將名稱修正為與欄位完全一致(此處為 y="Score"),同一呼叫即可繪製圖形。當不確定正確拼寫時,列印 data.columns 並從中複製名稱;此檢查對 x、y 和 hue 同樣適用。

鬚線是讀者會遇到的另一個邊界。向分數中加入一個遠離的值 60:四分位數幾乎不變(第一四分位數 15,中位數 18,第三四分位數 21),在預設 whis=1.5 下,上鬚線停在 24,而 60 被繪製為其外側的一個孤點。將 whis 作為百分位數對傳入,則鬚線會改為延伸到資料的這些百分位數:

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

scores = pd.DataFrame({"Score": [12, 14, 15, 16, 18, 19, 21, 24, 60]})
ax = sns.boxplot(data=scores, y="Score", whis=(0, 100))
plt.show()

使用 whis=(0, 100) 時,鬚線到達最小值 12 和最大值 60,其外側不再留有任何點。除非完整範圍本身就是要傳達的資訊,否則應優先使用預設值:拉長的鬚線會掩蓋極端值的異常程度。另請注意,空欄位不會引發錯誤——Seaborn 會回傳一個空的座標軸——因此當資料來自管線時,請在繪圖前檢查列數。