用 plt.scatter() 把多维数据画进一张散点图

2026-05-25 14 预计阅读时间:1 分钟
来源:realpython.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:8 分钟

散点图是探索数据关系时最直觉的工具。但很多开发者只会画最基础的 x-y 点阵,遇到多变量场景就束手无策——要么画一堆子图,要么把信息硬塞进图例。plt.scatter() 其实提供了四种视觉通道:大小、颜色、形状、透明度,足够在一张图里同时编码四到五个维度。

下面从基础用法开始,逐步叠加这些通道,最后给出一个可直接运行的综合示例。

最简散点图:两个维度就够了

import matplotlib.pyplot as plt
import numpy as np

np.random.seed(42)
x = np.random.normal(5, 2, 80)
y = x * 1.5 + np.random.normal(0, 3, 80)

plt.scatter(x, y)
plt.xlabel("收入(万元)")
plt.ylabel("消费(万元)")
plt.title("收入与消费散点图")
plt.tight_layout()
plt.show()

这是入门级用法:只传 x 和 y。点的大小、颜色、形状全是默认值。信息密度低,但够用——前提是你只关心两个变量。

用颜色编码第三个维度

当数据自带分组标签或连续数值时,c 参数是最自然的扩展方式。

# 假设每条数据有一个"年龄段"标签:0=青年, 1=中年, 2=老年
age_group = np.random.randint(0, 3, 80)

plt.scatter(x, y, c=age_group, cmap="Set2", edgecolors="black")
plt.colorbar(label="年龄段(0=青年 1=中年 2=老年)")
plt.xlabel("收入(万元)")
plt.ylabel("消费(万元)")
plt.tight_layout()
plt.show()

几个要点:

  • c 接受与数据等长的数组,可以是分类标签也可以是连续值。
  • cmap 控制颜色映射方案。分类数据用 "Set2""tab10" 这类离散色板;连续数据更适合 "viridis""coolwarm"
  • edgecolors 给点加描边,防止浅色点在白底上消失。
  • plt.colorbar() 是必须的——没有色条,颜色就是哑巴信息。

用大小编码第四个维度

s 参数控制每个点的面积(注意是像素面积,不是半径)。用它编码一个连续变量,比如"家庭人口数":

family_size = np.random.randint(1, 8, 80)

plt.scatter(
    x, y,
    c=age_group,
    s=family_size * 30,   # 放大系数让差异肉眼可见
    cmap="Set2",
    edgecolors="black",
    alpha=0.7
)
plt.colorbar(label="年龄段")
plt.xlabel("收入(万元)")
plt.ylabel("消费(万元)")
plt.tight_layout()
plt.show()

实操建议:

  • 原始数值往往太小或太大,需要乘一个系数做视觉缩放。先画一次看效果,再调整。
  • s 的值域跨度太大时,小点会被大点完全遮挡——这时就该引入透明度。

透明度:解决遮挡的最后一块拼图

alpha 取值 0 到 1,控制点的透明程度。数据量大或大小差异显著时,设 alpha=0.50.7 是常见做法:

  • 重叠区域会自然变暗,形成密度热力效果。
  • 小点不再被大点吞掉。

不要设到 0.2 以下——点太淡等于没画。

形状:第五个维度的可选通道

marker 参数可以改变点的形状,但 plt.scatter()marker 是全局参数,不支持逐点设置。要做多形状散点图,需要按组拆分多次调用:

markers = ["o", "s", "D"]  # 圆形、方形、菱形
labels = ["青年", "中年", "老年"]

for g in range(3):
    mask = age_group == g
    plt.scatter(
        x[mask], y[mask],
        marker=markers[g],
        s=family_size[mask] * 30,
        label=labels[g],
        alpha=0.7,
        edgecolors="black"
    )

plt.legend()
plt.xlabel("收入(万元)")
plt.ylabel("消费(万元)")
plt.tight_layout()
plt.show()

形状通道适合分类标签少于 5 种的场景。超过 5 种形状,人眼很难快速区分,不如回到颜色。

综合实战:一张图编码五个变量

把上面的技巧组合起来,下面是完整可运行示例。模拟一个"城市居民消费"数据集,同时展示收入、消费、年龄段、家庭规模、信用评分五个维度:

import matplotlib.pyplot as plt
import numpy as np

np.random.seed(42)
n = 200

# 生成模拟数据
income = np.random.normal(8, 3, n)                       # 收入(万元)
spending = income * 1.2 + np.random.normal(0, 4, n)      # 消费(万元)
age_group = np.random.randint(0, 3, n)                   # 年龄段
family_size = np.random.randint(1, 7, n)                 # 家庭人口
credit_score = np.clip(income * 10 + np.random.normal(0, 20, n), 300, 900)  # 信用评分

markers = ["o", "s", "D"]
group_labels = ["青年", "中年", "老年"]

fig, ax = plt.subplots(figsize=(8, 6))

for g in range(3):
    mask = age_group == g
    scatter = ax.scatter(
        income[mask],
        spending[mask],
        c=credit_score[mask],
        s=family_size[mask] * 40,
        marker=markers[g],
        cmap="coolwarm",
        alpha=0.65,
        edgecolors="black",
        linewidths=0.5,
        label=group_labels[g],
    )

cbar = fig.colorbar(scatter, ax=ax)
cbar.set_label("信用评分")

# 图例中用统一大小,避免大小干扰图例识别
handles, labels = ax.get_legend_handles_labels()
for h in handles:
    h.set_sizes([80])

ax.legend(handles, labels, title="年龄段")

ax.set_xlabel("收入(万元)")
ax.set_ylabel("消费(万元)")
ax.set_title("城市居民消费多维散点图")
ax.tight_layout()
plt.show()

这张图里:

  • x 轴:收入
  • y 轴:消费
  • 颜色:信用评分(连续,coolwarm 色板)
  • 大小:家庭人口
  • 形状:年龄段(三类)

五个维度,一张图,没有子图堆叠,没有信息遗漏。

实用清单

画多维散点图前快速检查这几项:

检查点 建议
维度超过 5 个? 不要硬塞,拆成两张图或换用交互式工具(Plotly、Bokeh)
点数超过 1000? 必须设 alpha,否则重叠区一片糊
颜色编码连续值? colorbar,选感知均匀色板(viridiscoolwarm
颜色编码分类值? 用离散色板(Set2tab10),加 legend
大小差异太极端? s 值做对数缩放:s=np.log(values)*50
形状超过 5 种? 放弃形状通道,回到颜色或拆图
图例里大小混乱? 手动 h.set_sizes([80]) 统一图例点尺寸

最后一点容易被忽略:图例里的点如果保留了原始大小,大点会撑破图例布局。统一图例点尺寸是专业散点图的基本礼仪。

plt.scatter() 的参数组合远不止这些——linewidthsedgecolorszorder 都能在特定场景下发挥作用。但大小、颜色、透明度、形状这四个核心通道,已经覆盖了绝大多数多维可视化的需求。下次画散点图时,先问自己"我到底要同时展示几个变量",再决定用哪些通道,而不是默认只画 x-y。


相关推荐