pyplot 散点图标记大小

634 投票
7 回答
1792720 浏览
提问于 2025-04-17 15:35

在pyplot的散点图文档中:

matplotlib.pyplot.scatter(x, y, s=20, c='b', marker='o', cmap=None, norm=None,
                              vmin=None, vmax=None, alpha=None, linewidths=None,
                              faceted=True, verts=None, hold=None, **kwargs)

标记的大小

s:单位是平方点。它可以是一个数值,也可以是一个和x、y长度相同的数组。

那么“平方点”是什么单位呢?这是什么意思?如果 s=100 ,是不是意味着 10像素 x 10像素

我主要是想制作不同标记大小的散点图,想弄清楚这个 s 的数字到底代表什么。

7 个回答

36

这是关于标记的面积。我的意思是,如果你有 s1 = 1000s2 = 4000,那么这两个圆的半径之间的关系是:r_s2 = 2 * r_s1。看看下面的图:

plt.scatter(2, 1, s=4000, c='r')
plt.scatter(2, 1, s=1000 ,c='b')
plt.scatter(2, 1, s=10, c='g')

enter image description here

我在看到这个帖子时也有同样的疑问,所以我做了这个例子,然后在屏幕上用尺子测量了半径。

364

因为其他回答说 s 代表标记的面积,所以我来澄清一下,这并不一定是对的。

大小以点为单位

plt.scatter 中,s 参数表示的是 markersize**2。就像文档中说的那样:

s : 标量或类似数组,形状为 (n, ),可选
大小以点的平方为单位。默认值是 rcParams['lines.markersize'] ** 2。

这可以字面理解。为了得到一个大小为 x 点的标记,你需要把这个数字平方后传给 s 参数。

所以,线图的标记大小和散点图的大小参数之间的关系是平方关系。为了让散点图的标记和大小为 10 点的线图标记一样大,你需要调用 scatter( .., s=100)

这里插入图片描述

import matplotlib.pyplot as plt

fig,ax = plt.subplots()

ax.plot([0],[0], marker="o",  markersize=10)
ax.plot([0.07,0.93],[0,0],    linewidth=10)
ax.scatter([1],[0],           s=100)

ax.plot([0],[1], marker="o",  markersize=22)
ax.plot([0.14,0.86],[1,1],    linewidth=22)
ax.scatter([1],[1],           s=22**2)

plt.show()

与“面积”的关系

那么,为什么其他回答甚至文档在谈到 s 参数时会提到“面积”呢?

当然,点的平方是面积单位。

  • 对于特殊情况的方形标记,marker="s",标记的面积确实直接等于 s 参数的值。
  • 对于圆形,圆的面积是 area = pi/4*s
  • 对于其他标记,可能与标记的面积没有明显关系。

这里插入图片描述

不过,在所有情况下,标记的面积与 s 参数是成比例的。这就是为什么称其为“面积”,即使在大多数情况下并不完全如此。

用与标记面积成比例的某个量来指定散点标记的大小是有道理的,因为在比较不同的标记时,感知到的是标记的面积,而不是边长或直径。也就是说,基础量翻倍应该使标记的面积翻倍。

这里插入图片描述

什么是点?

到目前为止,散点标记的大小是以点为单位的。点通常用于排版,字体的大小也是用点来表示的。线宽也常常用点来表示。在 matplotlib 中,标准的点大小是每英寸 72 点(ppi)——因此 1 点等于 1/72 英寸。

能够用像素而不是点来指定大小可能会很有用。如果图形的 dpi 也是 72,那么 1 点就是 1 像素。如果图形的 dpi 不同(matplotlib 的默认值是 fig.dpi=100),

1 point == fig.dpi/72. pixels

因此,散点标记的大小在点数上会因图形 dpi 的不同而有所不同,但可以制作一个 10 x 10 像素的标记,这样无论如何都会覆盖相同数量的像素:

这里插入图片描述 这里插入图片描述 这里插入图片描述

import matplotlib.pyplot as plt

for dpi in [72,100,144]:

    fig,ax = plt.subplots(figsize=(1.5,2), dpi=dpi)
    ax.set_title("fig.dpi={}".format(dpi))

    ax.set_ylim(-3,3)
    ax.set_xlim(-2,2)

    ax.scatter([0],[1], s=10**2, 
               marker="s", linewidth=0, label="100 points^2")
    ax.scatter([1],[1], s=(10*72./fig.dpi)**2, 
               marker="s", linewidth=0, label="100 pixels^2")

    ax.legend(loc=8,framealpha=1, fontsize=8)

    fig.savefig("fig{}.png".format(dpi), bbox_inches="tight")

plt.show() 

如果你对数据单位的散点图感兴趣,可以查看 这个回答

680

这种定义大小的方式可能有点让人困惑,但其实你是在指定标记的面积。这意味着,如果你想把标记的宽度(或高度)加倍,你需要把s增加4倍。[因为面积A = 宽W × 高H,所以(2W)(2H)=4A]

不过,标记的大小是这样定义是有原因的。因为面积是宽度的平方,所以当你把宽度加倍时,实际看起来大小会比加倍还要大(实际上是增加了4倍)。为了更好理解,可以看看下面两个例子及其输出。

# doubling the width of markers
x = [0,2,4,6,8,10]
y = [0]*len(x)
s = [20*4**n for n in range(len(x))]
plt.scatter(x,y,s=s)
plt.show()

结果是

enter image description here

注意到大小增加得非常快。如果我们改成

# doubling the area of markers
x = [0,2,4,6,8,10]
y = [0]*len(x)
s = [20*2**n for n in range(len(x))]
plt.scatter(x,y,s=s)
plt.show()

结果是

enter image description here

现在标记的明显大小增加得比较线性,符合直觉。

至于“点”的确切含义,对于绘图来说是相对随意的,你可以把所有的大小都按一个常数放大,直到看起来合适为止。

编辑:(回应@Emma的评论)

可能是我用词不够清晰。问题是关于把一个圆的宽度加倍,所以在第一张图中,每个圆(从左到右)宽度都是前一个的两倍,这样面积就是以4为底的指数增长。同样,第二个例子中每个圆的面积是前一个的两倍,这样就是以2为底的指数增长。

不过,第二个例子(我们在放大面积)中,面积加倍看起来圆的大小也加倍。因此,如果我们想让一个圆看起来大n倍,我们应该把面积增加n倍,而不是半径,这样明显的大小就会和面积成线性关系。

编辑 为了更好地理解@TomaszGandor的评论:

这是不同标记大小函数的效果:

Exponential, Square, or Linear size

x = [0,2,4,6,8,10,12,14,16,18]
s_exp = [20*2**n for n in range(len(x))]
s_square = [20*n**2 for n in range(len(x))]
s_linear = [20*n for n in range(len(x))]
plt.scatter(x,[1]*len(x),s=s_exp, label='$s=2^n$', lw=1)
plt.scatter(x,[0]*len(x),s=s_square, label='$s=n^2$')
plt.scatter(x,[-1]*len(x),s=s_linear, label='$s=n$')
plt.ylim(-1.5,1.5)
plt.legend(loc='center left', bbox_to_anchor=(1.1, 0.5), labelspacing=3)
plt.show()

撰写回答