解决 Matplotlib Scatter 不支持 Marker 列表的问题:mscatter 实现
Matplotlib
Python
Scatter Plot
Markers
Visualization
Data Analysis
Issue 11155
Published on
Matplotlib 的 standard plt.scatter 函数虽然允许传入列表来定义每个点的颜色 (c) 和大小 (s),但原生不支持传入列表来定义形状 (marker)。尝试传入 marker=['o', 'x', ...] 会导致错误。
这是 Matplotlib 长期存在的一个 Feature Request (参考 Issue #11155)。
解决方案
使用自定义函数 mscatter。该函数通过直接替换底层 PathCollection 中的 paths 对象,暴力实现了为每个点指定不同几何形状的功能。
代码实现
import matplotlib.pyplot as plt
import matplotlib.markers as mmarkers
def mscatter(x, y, s, ax=None, m=None, **kw):
"""
绘制散点图,支持为每个点指定不同的 marker 形状。
参数:
x, y : array-like
数据点坐标
s : scalar or array-like
点的大小 (size)
ax : matplotlib.axes.Axes, optional
绘图坐标轴对象,默认为 plt.gca()
m : list
marker 形状列表,长度必须与 x 相同 (e.g. ['o', 'v', 'x', ...])
**kw :
传递给 scatter 的其他参数 (如 c, alpha 等)
"""
if not ax:
ax = plt.gca()
# 1. 先调用标准 scatter
sc = ax.scatter(x, y, s, **kw)
# 2. 如果传入了 marker 列表 m,则替换路径
if (m is not None) and (len(m) == len(x)):
paths = []
for marker in m:
if isinstance(marker, mmarkers.MarkerStyle):
marker_obj = marker
else:
marker_obj = mmarkers.MarkerStyle(marker)
# 获取 marker 的路径并应用变换
path = marker_obj.get_path().transformed(marker_obj.get_transform())
paths.append(path)
# 3. 核心 hack:设置 PathCollection 的路径
sc.set_paths(paths)
return sc
使用示例
import numpy as np
# 生成测试数据
N = 10
x = np.random.rand(N)
y = np.random.rand(N)
s = np.random.randint(50, 200, size=N) # 随机大小
c = np.random.rand(N) # 随机颜色
# 定义不同的 marker 列表
# 注意:长度必须与数据长度一致
markers = ['o', 'v', '^', '<', '>', '1', '2', '3', '4', 's']
# 绘图
fig, ax = plt.subplots()
mscatter(x, y, s=s, c=c, m=markers, ax=ax)
plt.show()
注意事项
- 性能:由于需要循环创建 Path 对象,如果数据量极大(例如几万个点),初始化速度可能会比原生
scatter慢。 - 图例 (Legend):自动生成的 Legend 可能无法正确显示混合的形状。如果需要 Legend,可能需要手动构建
Line2D对象作为 proxy artists。