HOWTO · Matplotlib

在 Matplotlib 散點圖中設定標記大小

瞭解如何在 Matplotlib 散點圖中設定統一或逐點標記大小、正確縮放標記面積,並修正長度不符的大小陣列。

本頁內容

在 Matplotlib 散點圖中設定標記大小時,請使用 Axes.scatters 引數。純量會為所有點套用相同的名義面積;一維清單或陣列則為每個點指定面積,長度必須與 xy 相同。

使用 s 設定標記大小

若要在 Matplotlib 散點圖中設定標記大小,請將 s 引數傳給 Axes.scatters=80 這類純量會讓所有點使用相同的標稱面積。一維串列或陣列可以為各點指定不同面積,但值的數量必須與 xy 相同。例如,一種大小可使用 ax.scatter(x, y, s=80),四個點使用不同大小可寫成 ax.scatter(x, y, s=[20, 40, 80, 160])

當每個觀測值需要不同大小時,請使用 scatter。相關的 plot 方法則以 markersize 指定一個線性標記尺寸。

為所有點設定相同大小或逐點設定大小

下列可重現的範例並排繪製一個採用純量大小的集合,以及一個依據資料計算大小的集合。它也將相同的值映射到顏色,並使用 alpha 讓重疊的標記更容易辨認。程式碼會儲存結果影像,因此即使沒有互動式後端也能檢查結果。

import matplotlib.pyplot as plt

x = [1, 2, 3, 4, 5]
y = [2, 3, 5, 7, 11]
values = [1, 2, 4, 7, 11]
sizes = [40 + value * 20 for value in values]

fig, (top, bottom) = plt.subplots(2, 1, figsize=(6, 7), layout="constrained")
uniform = top.scatter(x, y, s=80, color="tab:blue")
varying = bottom.scatter(x, y, s=sizes, c=values, cmap="viridis", alpha=0.75)

top.set(title="s = 80", xlabel="x", ylabel="y")
bottom.set(title="s = [60, 80, 120, 180, 260]", xlabel="x", ylabel="y")
fig.colorbar(varying, ax=bottom)

print("scalar sizes:", uniform.get_sizes().tolist())
print("per-point sizes:", varying.get_sizes().tolist())
fig.savefig("scatter-marker-sizes.webp", dpi=120)

兩個 PathCollection 回報 Matplotlib 實際保留的 s 值:

scalar sizes: [80]
per-point sizes: [60, 80, 120, 180, 260]

兩個 Matplotlib 散點圖,比較統一的純量標記面積與五個逐點標記面積。

這項轉換從 40 開始,每增加一個資料單位就增加 20。因此,最小值仍然可見,最大值也不會蓋住座標軸。對於實際資料,應選用有明確含義的大小上下限,而不要直接傳入極端的原始值。顏色可以編碼同一變數,也可以編碼另一個變數;只要顏色代表數量,就應加入色彩條。

瞭解平方點和預設值

Matplotlib 的 scatter 參考文件規定,s 的單位是平方點(pt²),其中一個排版點等於 1/72 英吋。預設值為 rcParams['lines.markersize'] ** 2。因此,s 與標稱標記面積成比例,但 s=100 並不保證可見形狀嚴格為 10×10 點。最終輪廓還會受標記路徑、圖形 DPI 和邊緣樣式影響。

邊緣對小型標記的影響尤其明顯。當 linewidths 大於零且 edgecolors 可見時,描邊以標記邊界為中心,有效外部尺寸會增加半個線寬。比較小型實心標記且不希望邊緣改變其視覺大小時,可使用 linewidths=0edgecolors="none"

標記大小是顯示空間中的度量,而不是 x 軸或 y 軸的資料單位。因此,縮放視圖或變更座標軸範圍不會讓標記代表資料座標中的半徑。

正確縮放標記面積和線性寬度

s 乘以 2 會讓標稱面積加倍。由於線性外部尺寸大致依面積的平方根成長,對形狀相同的標記而言,將 s 乘以 4 會使寬度約為原來的兩倍。這是比例關係上的視覺規則,而非對每一種標記路徑的精確幾何保證。

import matplotlib.pyplot as plt

base = 36
area_sizes = [base, 2 * base, 4 * base]

fig, (top, bottom) = plt.subplots(2, 1, figsize=(6, 7), layout="constrained")
top.scatter([1, 2, 3], [1, 1, 1], s=area_sizes, linewidths=0)
top.set(title="s = [36, 72, 144]", yticks=[])
top.set_xticks([1, 2, 3], ["1", "2", "4"])

bottom.scatter([1], [1], s=10**2, linewidths=0)
(line,) = bottom.plot([2], [1], marker="o", linestyle="none", markersize=10,
                     color="tab:orange")
bottom.set(title="scatter: s = 10**2    plot: markersize = 10", xlim=(0.5, 2.5), yticks=[])

print("scatter areas:", area_sizes)
print("plot markersize:", line.get_markersize())
fig.savefig("scatter-size-scaling.webp", dpi=120)

所選值與 Line2D 標記尺寸如下:

scatter areas: [36, 72, 144]
plot markersize: 10.0

Matplotlib 輸出,比較散點面積倍數,以及 scatter 的 s 和 plot 的 markersize。

選擇 scatter(s=...)plot(markersize=...)

繪製散點圖時應使用 scatter(s=...),特別是每個觀測值需要不同面積或顏色時。若要繪製所有節點共用一種標記大小的 Line2D,請使用 plot(marker="o", markersize=10)。如 Matplotlib 的 plot 參考文件所述,markersize(或 ms)是以點為單位的線性大小,並非 scatter 接受的平方點面積。

對圓形標記而言,scatter(s=10**2)plot(markersize=10) 表達的是彼此對應的標稱尺寸,但不應依賴像素層級的完全一致。由於正規化路徑、邊緣、算繪後端和點陣化方式不同,集合標記與線條標記可能會有差異。

修正與點數不符的大小陣列

非純量 s 的長度必須與繪製點的數量相同。下列範例會擷取目前 Matplotlib 的診斷訊息,以便在不顯示完整回溯的情況下輸出錯誤:

import matplotlib.pyplot as plt

x = [1, 2, 3]
y = [1, 4, 9]

try:
    plt.scatter(x, y, s=[40, 80])
except ValueError as error:
    print(f"{type(error).__name__}: {error}")
else:
    raise AssertionError("a mismatched size array should fail")

在 Matplotlib 3.11.2 中,輸出如下:

ValueError: s (size 2) cannot be broadcast to match x and y (size 3)

較舊版本的 Matplotlib 可能使用不同的 ValueError 文字,但修正方式相同:三個 (x, y) 點應提供三個大小值,或僅傳入一個純量大小。