目录6

文章Matplotlib第 21 章

稀疏矩阵结构

Matplotlib · 第 21 / 43 章

spy() —— 稀疏矩阵结构

spy() 只回答一个问题:矩阵里哪些位置非零。值多大不重要,所以它把所有非零点画成同一种颜色的小方块。用途:检查稀疏矩阵的带宽、对角线结构、分块结构;调试「这个矩阵到底有多稀疏」。

Python9 行
Axes.spy(
    Z,                    # ndarray 或 scipy.sparse 矩阵
    precision=0,          # 小于等于此值的当作 0
    marker=None,          # 给了 marker 就切到标记模式
    markersize=None,      # 标记大小
    aspect="equal",       # ★ 非方阵要改成 'auto'
    origin="upper",       # 同 imshow
    **kwargs
)
参数 一句话 默认值的影响
Z 矩阵 支持 scipy.sparse,不用先 .toarray()
precision 零的阈值 0:任何非零都画
marker 标记形状 None → 走 imshow;给了 → 走 Line2D
markersize 标记大小 仅标记模式有效
aspect 格子形状 'equal',非方阵会被压扁
origin 原点位置 'upper'

precision:把小值当零

Python13 行
import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(42)
Z = np.zeros((20, 20))
idx = rng.choice(400, 60, replace=False)
Z.flat[idx] = rng.random(60) * 0.9 + 0.1
Z[3, 7] = 0.02          # 两个「几乎为零」的杂质
Z[12, 15] = 0.03

fig, ax = plt.subplots()
ax.spy(Z)
plt.show()

稀疏矩阵结构图

Python2 行
ax.spy(Z)                    # precision=0:0.02 和 0.03 也算非零,画出来
ax.spy(Z, precision=0.1)     # 小于 0.1 的当作 0,那两个格子消失

这不是「灵敏度调节」,而是数值噪声过滤:迭代求解器、近似求逆常常留下 1e-17 这种残渣,precision=1e-10 才能看到真实的稀疏结构。

spy 参数:precision / marker / aspect

marker:换个渲染后端

这一个参数会把返回类型换掉:

Python7 行
sp = ax.spy(Z)
print(type(sp).__name__)
# 输出: AxesImage

sp = ax.spy(Z, marker="s", markersize=5)
print(type(sp).__name__)
# 输出: Line2D

不传 marker → 底层用 imshow 渲染,一个像素块是一个非零元素,大矩阵更快。 传 marker → 每个非零点变成一个 marker,返回单个 Line2D(不是 list!),风格更统一、可以逐点设边色,但大矩阵会明显变慢。

markersize 只在标记模式下有效,imshow 模式下改它没有任何效果。

aspect:非方阵必须改

Python1 行
ax.spy(Z[:8, :20], aspect="auto")     # 8×20 的矩阵

默认 'equal' 会把它压成一条扁带——和 imshow / matshow 完全一样的坑。

返回对象
Python14 行
sp = ax.spy(np.eye(4))
print(type(sp).__name__)
# 输出: AxesImage

if hasattr(sp, "set_cmap"):            # imshow 模式
    sp.set_cmap("gray")
    sp.set_clim(0, 1)
else:                                  # marker 模式
    sp.set_markersize(5)
    sp.set_markerfacecolor("#C44E52")

sp2 = ax.spy(np.eye(4), marker="s")
print(type(sp2).__name__, len(sp2.get_xdata()))
# 输出: Line2D 4

稳妥写法就是上面这个 hasattr 判断——返回类型取决于有没有传 marker,这是 spy 唯一需要小心的地方。

注意事项

  • 返回类型随 marker 变化:不给 marker 是 AxesImage,给了是 Line2D。
  • marker 模式下返回的是一个 Line2D,不是 list,不要 len()。
  • aspect 默认 'equal',非方阵会被压扁。
  • precision 是「小于等于就当作零」,不是相对阈值。
  • scipy.sparse 矩阵可以直接传,不需要 .toarray()——反而转成稠密矩阵可能直接撑爆内存。
  • 大矩阵别用 marker 模式,逐个画 marker 会慢到不可用。

综合示例:三种稀疏结构

Python27 行
import numpy as np
import matplotlib.pyplot as plt

plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei", "DejaVu Sans"]
plt.rcParams["axes.unicode_minus"] = False

n = 60
diag = np.diag(np.ones(n)) + np.diag(np.ones(n - 1), 1) + np.diag(np.ones(n - 1), -1)
band = np.diag(np.ones(n)) * 4 + np.diag(np.ones(n - 2), 2) + np.diag(np.ones(n - 2), -2)

rng = np.random.default_rng(6)
blocky = np.zeros((n, n))
for b in range(0, n, 12):
    blocky[b:b + 12, b:b + 12] = rng.random((12, 12)) * 0.9 + 0.1
blocky[blocky < 0.55] = 0

fig, axes = plt.subplots(1, 3, figsize=(14, 4.6))

for ax, mat, title in zip(
        axes,
        [diag, band, blocky],
        ["(a) 三对角矩阵", "(b) 带状矩阵(带宽 2)", "(c) 分块结构"]):
    ax.spy(mat, markersize=3, color="#1f77b4")
    ax.set_title(title)

plt.tight_layout()
plt.show()

三个图一眼就能读出矩阵的结构:(a) 一条对角带、(b) 带宽为 2、(c) 每 12 个一组的块——这正是 spy 存在的意义,换成 imshow 反而要跟色阶和抗锯齿纠缠。