一张散点图能同时呈现两组数值的分布与关联,这是折线图和柱状图做不到的。Python 里 matplotlib.pyplot.scatter() 是画散点图的主力函数,但它不只是把点画上去——每个点的尺寸、颜色、形状、透明度都可以单独控制,这意味着你能在一张图里编码四五个维度的信息。
下面从最基础的调用开始,逐步把散点图从"能看"变成"好看且有用"。
最简调用:先让点出现在画布上
plt.scatter() 的两个核心参数是 x 和 y,即每个点的横纵坐标。最简单的用法:
import matplotlib.pyplot as plt
x = [1, 2, 3, 4, 5]
y = [2, 4, 1, 3, 5]
plt.scatter(x, y)
plt.show()
这会生成一张默认蓝色圆点散点图。能跑,但信息密度很低——所有点长得一模一样,看不出谁重谁轻。
用尺寸编码第三个维度
s 参数控制每个点的大小,单位是点的平方(points²)。传入一个与数据等长的列表,就能让点的大小随某个变量变化:
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(42)
x = np.random.rand(50)
y = np.random.rand(50)
sizes = np.random.rand(50) * 500 # 随机大小,范围 0~500
plt.scatter(x, y, s=sizes)
plt.show()
sizes 的值越大,点越醒目。实际场景里,你可以把城市人口、销售额、文件体积等映射到 s,让读者一眼看出"谁是大玩家"。
注意:s 的值是面积的量级,不是直径。从 10 到 100 的变化在视觉上远没有从 10 到 1000 那么夸张,调参时建议先画出来看效果,再决定数值范围。
用颜色编码第四个维度
c 参数接受颜色值,可以是单个颜色字符串,也可以是一个与数据等长的数值列表。数值列表会配合 cmap 色彩映射,自动把数值映射到渐变色带:
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(7)
x = np.random.rand(80)
y = np.random.rand(80)
colors = np.random.rand(80) # 0~1 的随机值,映射到 colormap
plt.scatter(x, y, c=colors, cmap="viridis")
plt.colorbar(label="某个指标") # 加色条,让读者知道颜色对应什么值
plt.show()
cmap 有几十种可选,常用的有 viridis(感知均匀、色盲友好)、plasma、coolwarm(适合正负对比)。选 colormap 不是审美问题——如果你的数据有零点,coolwarm 比纯渐变更直观;如果要打印黑白,gray 更安全。
透明度:解决点重叠的杀手锏
数据量大时,点会叠在一起,中心区域变成一团黑,看不出密度差异。alpha 参数控制透明度,0 为全透明,1 为全不透明:
plt.scatter(x, y, s=80, alpha=0.3)
把 alpha 设到 0.2~0.5 之间,重叠区域自然变深,稀疏区域保持浅色,相当于免费获得了一张密度热力图。这是处理大规模散点数据最实用的一个参数。
标记形状:区分类别而非数值
marker 参数改变点的形状。默认是 "o"(圆圈),还可以用 "^"(三角形)、"s"(方形)、"D"(菱形)、"*"(星形)等。形状适合编码类别信息(比如不同物种、不同实验组),而尺寸和颜色更适合编码连续数值。
如果只有两三个类别,可以分次调用 plt.scatter(),每次用不同 marker,再配合 label 和 plt.legend():
group_a = np.random.rand(20, 2)
group_b = np.random.rand(20, 2) + 0.5
plt.scatter(group_a[:, 0], group_a[:, 1], marker="o", label="组 A")
plt.scatter(group_b[:, 0], group_b[:, 1], marker="^", label="组 B")
plt.legend()
plt.show()
一张图编码五个维度:完整示例
把上面所有参数组合起来,就能在一张散点图里同时展示横轴、纵轴、点大小、点颜色、点形状。下面是一个可直接运行的完整示例,模拟城市数据——横轴为经度、纵轴为纬度、大小为人口、颜色为年均气温、形状区分沿海与内陆:
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(2024)
n = 60
longitude = np.random.uniform(100, 130, n) # 经度
latitude = np.random.uniform(20, 45, n) # 纬度
population = np.random.uniform(50, 2000, n) # 人口(万)
temperature = np.random.uniform(5, 25, n) # 年均气温(℃)
is_coastal = np.random.choice([True, False], n) # 是否沿海
# 人口映射到点面积,加一个下限避免点太小看不见
sizes = population / 2000 * 600 + 20
# 沿海用圆点,内陆用方形
coastal_mask = is_coastal
inland_mask = ~is_coastal
plt.figure(figsize=(10, 7))
plt.scatter(
longitude[coastal_mask],
latitude[coastal_mask],
s=sizes[coastal_mask],
c=temperature[coastal_mask],
cmap="coolwarm",
alpha=0.6,
marker="o",
label="沿海城市",
edgecolors="grey",
linewidths=0.5,
)
plt.scatter(
longitude[inland_mask],
latitude[inland_mask],
s=sizes[inland_mask],
c=temperature[inland_mask],
cmap="coolwarm",
alpha=0.6,
marker="s",
label="内陆城市",
edgecolors="grey",
linewidths=0.5,
)
cbar = plt.colorbar(label="年均气温 (℃)")
plt.xlabel("经度")
plt.ylabel("纬度")
plt.title("模拟城市分布:大小 = 人口,颜色 = 气温,形状 = 沿海/内陆")
plt.legend(loc="upper left")
plt.tight_layout()
plt.show()
运行后你会看到一张信息密度很高的图:大城市一目了然,气温冷暖靠色带区分,沿海和内陆靠形状区分,重叠区域因透明度自然呈现密度。改掉 np.random 部分换成真实数据(比如 pandas DataFrame),就能直接用在报告里。
实用调参清单
画散点图时容易踩的坑和对应策略:
| 问题 | 对策 |
|---|---|
| 点太密集看不清分布 | 降低 alpha 到 0.2~0.4 |
| 大点遮住小点 | 先画大点、后画小点(调用顺序即绘制层级),或给点加 edgecolors 描边 |
| 颜色映射看不懂 | 加 plt.colorbar() 并设 label |
| 图例只显示一个标记 | 分组多次调用 scatter(),每次传 label |
| 点太小在演示时看不见 | s 最低值不要低于 15,演示场景建议整体放大 |
| 打印后黑白图丢失信息 | 避免用红绿对比,选 viridis 或直接用形状区分 |
最后一点:plt.scatter() 每次调用都会创建一个独立的 PathCollection,数据量上万时性能会明显下降。如果只是画纯散点不需要逐点定制大小和颜色,plt.plot(x, y, "o") 更快——它把所有点当成一条线的标记来渲染,速度能快几倍。需要逐点定制时才用 scatter(),这是它真正的价值所在。