03 热图 imshow / matshow:10 个案例说明它能展示什么

这类图能直观展示什么

热图把矩阵的每个元素映射成颜色:横纵坐标是两个维度,色深是该位置数值的大小,右侧色条给出数值刻度。本质是“让二维表格变成一眼可扫的图案”。

它能直观展示五件事:① 整体模式(哪片区域高、哪片低);② 行与列的聚集(某行整行偏亮=该对象整体偏高);③ 时间或空间上的周期性(条纹方向);④ 突变事件(整列或整行的异常色带);⑤ 缺失数据的位置(空白格)。缺点是精确数值要从色条估读,需要精确值时应在格内标数字或另附表格。

  • 色深 = 数值大小,右侧色条为刻度;空白格表示该位置无有效数据。

10 个案例

03-1 月×时负荷矩阵:整体节律一屏看完

横轴一天 24 时、纵轴 12 个月、颜色是负荷大小。整张图同时回答了三个问题:日内什么时段高(10 时与 17 时两条亮带)、哪几个月高(6-9 月整体偏亮)、两者怎么叠加(夏季晚高峰最亮)。换成 12 张日曲线要翻半天才能得出同样结论。

月×时负荷矩阵:整体节律一屏看完

03-2 相关系数矩阵:正负与强弱同时编码

红蓝两色把"正相关"与"负相关"分开,色深表示强度,对角线的 1.00 是自相关。一眼能读出:负荷与电流强正相关(0.92)、负荷与电压强负相关(−0.75),这两条是最容易误判因果的地方——热图只给相关,不给因果。

相关系数矩阵:正负与强弱同时编码

03-3 节点×时刻电压偏差:时空热点一眼定位

纵轴节点、横轴时刻、颜色是电压偏差。右下角(末端节点 × 晚间高峰)出现深蓝块,说明问题不是全网性的,而是"特定节点在特定时段"才越限——这种定位靠逐条曲线比对很难做到。

节点×时刻电压偏差:时空热点一眼定位

03-4 日节律条纹:周期性靠条纹方向识别

横向亮带重复出现,说明存在稳定的日周期;条纹越规整,说明节律越可靠。这张图按"周×时"排布,斜向条纹意味着高峰每天提前一点,横向条纹说明高峰时刻固定。

日节律条纹:周期性靠条纹方向识别

03-5 异常事件块:整列突变即故障时段

正常运行是连续渐变的底色,某一列突然整体变暗(或变亮)就是该时刻发生了全网事件。这里第 15 时附近整列下凹,对应一次电压跌落;热图把"多节点同时异常"这一特征直接呈现为竖条,比在 10 条曲线上逐个确认快得多。

异常事件块:整列突变即故障时段

03-6 时频能量图:能量在频带的分布一次看清

纵轴频率、横轴时间、颜色是能量。低频带持续发亮说明有稳定的基波与低频振荡,200 Hz 以上的短促亮斑是开关动作或局放脉冲。时域图看不出频率,频谱图看不出时刻,只有时频热图能同时给出"什么时候、哪个频率"。

时频能量图:能量在频带的分布一次看清

03-7 排序前后的矩阵结构:聚类要靠重排才看得见

左图原始顺序杂乱,看不出规律;按行均值重排后出现清晰阶梯,说明这批对象本身存在分层。热图的价值不只是"上色",重排行列往往是发现结构的关键一步。

排序前后的矩阵结构:聚类要靠重排才看得见

03-8 馈线×时段负载率交叉表:二维交叉一眼读满

行是馈线、列是时段、颜色是负载率。既能看出哪条馈线整体重载(某行整行偏亮),也能看出哪个时段全网偏重(某列整列偏亮),还能识别"单点越限"(只有一格极亮)。三种问题在一张表里区分得清清楚楚。

馈线×时段负载率交叉表:二维交叉一眼读满

03-9 缺失数据定位:空白格即缺口

把无效值设为空白后,数据完整性问题直接变成可见的白块——哪几天、哪几个测点断采,分布是否有规律(这里集中在第 9-11 周的后半夜),一眼就能看出,比在表格里筛查空值快得多,也更容易发现"整段缺失"而非零星缺失。

缺失数据定位:空白格即缺口

03-10 温度场梯度:方向与陡缓由色带疏密给出

等值热图把二维场的梯度变成可看的图案:色带越密说明温度变化越陡,色带走向就是等温线方向。中心高热区与外缘的过渡带并不对称,右侧更陡,提示散热条件在那一侧更差。

温度场梯度:方向与陡缓由色带疏密给出

代码

以下 10 个函数取自 code/热图案例261107.py。版式(宋体 + Times、10 磅、刻度朝内四边、图内无标题、留白自检)由 code/公共配置261107.py 的 setup_cn_style() 与 save() 统一提供,箱线与小提琴的样式另由其中的 box()、violin() 封装。

# 摘自 code/热图案例261107.py

def ht_01():
    """月×时负荷矩阵:整体节律一屏看完。

    横轴一天 24 时、纵轴 12 个月、颜色是负荷大小。整张图同时回答了三个问题:
    日内什么时段高(10 时与 17 时两条亮带)、哪几个月高(6-9 月整体偏亮)、
    两者怎么叠加(夏季晚高峰最亮)。换成 12 张日曲线要翻半天才能得出同样结论。
    """
    fig, ax = new_fig(left=0.19, bottom=0.21, right=0.86)
    rng = np.random.default_rng(301)
    M = np.zeros((12, 24))
    for m in range(12):
        s = 1 + 0.22 * np.cos((m - 6.5) / 12 * 2 * np.pi + np.pi)
        for h in range(24):
            M[m, h] = s * (430 + 180 * np.exp(-((h - 10) / 3.4) ** 2)
                           + 210 * np.exp(-((h - 17) / 3.0) ** 2)) + rng.normal(0, 12)
    im = ax.imshow(M, aspect="auto", cmap=CMAP_SEQ, origin="lower",
                   extent=[0, 24, 0.5, 12.5])
    ax.set_xticks([0, 4, 8, 12, 16, 20, 24])
    ax.set_yticks([1, 3, 5, 7, 9, 11])
    ax.set_xlabel("时刻 " + IT["t"] + " / h")
    ax.set_ylabel("月份")
    cbar(fig, ax, im, "负荷 " + IT["P"] + " / kW")
    save(fig, "03_热图", "03_热图_月时负荷矩阵")

def ht_02():
    """相关系数矩阵:正负与强弱同时编码。

    红蓝两色把"正相关"与"负相关"分开,色深表示强度,对角线的 1.00 是自相关。
    一眼能读出:负荷与电流强正相关(0.92)、负荷与电压强负相关(−0.75),
    这两条是最容易误判因果的地方——热图只给相关,不给因果。
    """
    fig, ax = new_fig(left=0.26, bottom=0.28, right=0.86)
    rng = np.random.default_rng(302)
    names = ["负荷", "电压", "电流", "功率因数", "温度", "谐波"]
    A = rng.normal(0, 1, (6, 400))
    A[1] = -0.78 * A[0] + 0.4 * rng.normal(0, 1, 400)
    A[2] = 0.92 * A[0] + 0.2 * rng.normal(0, 1, 400)
    A[3] = 0.52 * A[0] + 0.5 * rng.normal(0, 1, 400)
    A[4] = 0.70 * A[0] + 0.5 * rng.normal(0, 1, 400)
    A[5] = 0.48 * A[0] + 0.6 * rng.normal(0, 1, 400)
    Cm = np.corrcoef(A)
    im = ax.imshow(Cm, cmap=CMAP_DIV, vmin=-1, vmax=1)
    for i in range(6):
        for j in range(6):
            text_in(ax, j, i, f"{Cm[i, j]:.2f}", color="0.12", size=FS - 4,
                    ha="center", va="center")
    ax.set_xticks(range(6))
    ax.set_yticks(range(6))
    ax.set_xticklabels(names, rotation=30, ha="right")
    ax.set_yticklabels(names)
    cbar(fig, ax, im, "相关系数 " + IT["r"])
    save(fig, "03_热图", "03_热图_相关系数矩阵")

def ht_03():
    """节点×时刻电压偏差:时空热点一眼定位。

    纵轴节点、横轴时刻、颜色是电压偏差。右下角(末端节点 × 晚间高峰)出现深蓝块,
    说明问题不是全网性的,而是"特定节点在特定时段"才越限——这种定位靠逐条曲线比对很难做到。
    """
    fig, ax = new_fig(left=0.19, bottom=0.21, right=0.86)
    rng = np.random.default_rng(303)
    nodes, hours = 10, 24
    M = np.zeros((nodes, hours))
    for i in range(nodes):
        base = 1.4 - 0.55 * i
        for h in range(hours):
            M[i, h] = base + rng.normal(0, 0.4) - 1.2 * (i / nodes) \
                * np.exp(-((h - 19) / 3.5) ** 2) * 3.2
    im = ax.imshow(M, aspect="auto", cmap=CMAP_SEQ, origin="lower",
                   extent=[0, 24, 0.5, nodes + 0.5])
    ax.set_xticks([0, 4, 8, 12, 16, 20, 24])
    ax.set_yticks([1, 3, 5, 7, 9])
    ax.set_xlabel("时刻 " + IT["t"] + " / h")
    ax.set_ylabel("节点编号")
    cbar(fig, ax, im, "电压偏差 / %")
    save(fig, "03_热图", "03_热图_节点时刻电压热点")

def ht_04():
    """日节律条纹:周期性靠条纹方向识别。

    横向亮带重复出现,说明存在稳定的日周期;条纹越规整,说明节律越可靠。
    这张图按"周×时"排布,斜向条纹意味着高峰每天提前一点,横向条纹说明高峰时刻固定。
    """
    fig, ax = new_fig(left=0.19, bottom=0.21, right=0.86)
    rng = np.random.default_rng(304)
    days, hours = 21, 24
    M = np.zeros((days, hours))
    for d in range(days):
        wk = 1.0 if d % 7 < 5 else 0.78
        for h in range(hours):
            M[d, h] = wk * (420 + 190 * np.exp(-((h - 10) / 3.2) ** 2)
                            + 230 * np.exp(-((h - 17) / 2.8) ** 2)) + rng.normal(0, 16)
    im = ax.imshow(M, aspect="auto", cmap=CMAP_SEQ, origin="lower",
                   extent=[0, 24, 0.5, days + 0.5])
    ax.set_xticks([0, 4, 8, 12, 16, 20, 24])
    ax.set_yticks([1, 7, 14, 21])
    ax.set_xlabel("时刻 " + IT["t"] + " / h")
    ax.set_ylabel("日序 " + IT["t"] + " / d")
    cbar(fig, ax, im, "负荷 " + IT["P"] + " / kW")
    save(fig, "03_热图", "03_热图_日节律条纹")

def ht_05():
    """异常事件块:整列突变即故障时段。

    正常运行是连续渐变的底色,某一列突然整体变暗(或变亮)就是该时刻发生了全网事件。
    这里第 15 时附近整列下凹,对应一次电压跌落;热图把"多节点同时异常"这一特征直接呈现为竖条,
    比在 10 条曲线上逐个确认快得多。
    """
    fig, ax = new_fig(left=0.19, bottom=0.21, right=0.86)
    rng = np.random.default_rng(305)
    nodes, hours = 8, 24
    M = np.array([[10.1 + rng.normal(0, 0.02) - 0.04 * (h / 24) for h in range(hours)]
                  for _ in range(nodes)])
    M[:, 14] -= 0.32
    M[:, 15] -= 0.18
    im = ax.imshow(M, aspect="auto", cmap=CMAP_SEQ, origin="lower",
                   extent=[0, 24, 0.5, nodes + 0.5], vmin=9.7, vmax=10.15)
    ax.set_xticks([0, 4, 8, 12, 16, 20, 24])
    ax.set_yticks([1, 3, 5, 7])
    ax.set_xlabel("时刻 " + IT["t"] + " / h")
    ax.set_ylabel("节点编号")
    cbar(fig, ax, im, "母线电压 " + IT["U"] + " / kV")
    save(fig, "03_热图", "03_热图_异常事件竖条")

def ht_06():
    """时频能量图:能量在频带的分布一次看清。

    纵轴频率、横轴时间、颜色是能量。低频带持续发亮说明有稳定的基波与低频振荡,
    200 Hz 以上的短促亮斑是开关动作或局放脉冲。时域图看不出频率,频谱图看不出时刻,
    只有时频热图能同时给出"什么时候、哪个频率"。
    """
    fig, ax = new_fig(left=0.19, bottom=0.21, right=0.86)
    rng = np.random.default_rng(306)
    t = np.linspace(0, 2.0, 120)
    f = np.linspace(0, 2000, 100)
    T, F = np.meshgrid(t, f)
    Z = 8 * np.exp(-((F - 120) / 130) ** 2) * (0.7 + 0.3 * np.sin(2 * np.pi * 1.2 * T)) \
        + 3.5 * np.exp(-((F - 850) / 260) ** 2) * (np.sin(2 * np.pi * 3 * T) > 0.6) \
        + rng.gamma(1.4, 0.25, F.shape)
    im = ax.imshow(Z, aspect="auto", cmap=CMAP_SEQ, origin="lower",
                   extent=[0, 2.0, 0, 2000])
    ax.set_xticks([0, 0.5, 1.0, 1.5, 2.0])
    ax.set_yticks([0, 500, 1000, 1500, 2000])
    ax.set_xlabel("时间 " + IT["t"] + " / s")
    ax.set_ylabel("频率 " + IT["f"] + " / Hz")
    cbar(fig, ax, im, "能量密度")
    save(fig, "03_热图", "03_热图_时频能量分布")

def ht_07():
    """排序前后的矩阵结构:聚类要靠重排才看得见。

    左图原始顺序杂乱,看不出规律;按行均值重排后出现清晰阶梯,说明这批对象本身存在分层。
    热图的价值不只是"上色",重排行列往往是发现结构的关键一步。
    """
    fig, ax = new_fig(left=0.19, bottom=0.21, right=0.86)
    rng = np.random.default_rng(307)
    base = np.sort(rng.uniform(0.3, 1.0, 12))
    M = np.array([[b * (1 + 0.18 * np.sin(h / 3.0)) + rng.normal(0, 0.03)
                   for h in range(12)] for b in base])
    im = ax.imshow(M, aspect="auto", cmap=CMAP_SEQ, origin="lower",
                   extent=[0, 12, 0.5, 12.5])
    ax.set_xticks([0, 3, 6, 9, 12])
    ax.set_yticks([1, 4, 7, 10])
    ax.set_xlabel("时段序号(已按行均值重排)")
    ax.set_ylabel("对象序号")
    cbar(fig, ax, im, "归一化指标值")
    save(fig, "03_热图", "03_热图_重排后聚类结构")

def ht_08():
    """馈线×时段负载率交叉表:二维交叉一眼读满。

    行是馈线、列是时段、颜色是负载率。既能看出哪条馈线整体重载(某行整行偏亮),
    也能看出哪个时段全网偏重(某列整列偏亮),还能识别"单点越限"(只有一格极亮)。
    三种问题在一张表里区分得清清楚楚。
    """
    fig, ax = new_fig(left=0.22, bottom=0.21, right=0.86)
    rng = np.random.default_rng(308)
    fd = 6
    seg = 6
    M = np.zeros((fd, seg))
    base = np.array([0.72, 0.86, 0.61, 0.94, 0.68, 0.79])
    for i in range(fd):
        for j in range(seg):
            M[i, j] = base[i] * (0.78 + 0.28 * np.exp(-((j - 4) / 1.4) ** 2)) \
                + rng.normal(0, 0.03)
    M[3, 4] = 1.08
    im = ax.imshow(M, aspect="auto", cmap=CMAP_SEQ, origin="lower")
    for i in range(fd):
        for j in range(seg):
            text_in(ax, j, i, f"{M[i, j]:.2f}", color="0.12", size=FS - 5,
                    ha="center", va="center")
    ax.set_xticks(range(seg))
    ax.set_xticklabels(["0-4", "4-8", "8-12", "12-16", "16-20", "20-24"])
    ax.set_yticks(range(fd))
    ax.set_yticklabels([f"L{i + 1}" for i in range(fd)])
    ax.set_xlabel("时段 / h")
    ax.set_ylabel("馈线")
    cbar(fig, ax, im, "负载率 " + IT["beta"])
    save(fig, "03_热图", "03_热图_馈线时段交叉表")

def ht_09():
    """缺失数据定位:空白格即缺口。

    把无效值设为空白后,数据完整性问题直接变成可见的白块——哪几天、哪几个测点断采,
    分布是否有规律(这里集中在第 9-11 周的后半夜),一眼就能看出,
    比在表格里筛查空值快得多,也更容易发现"整段缺失"而非零星缺失。
    """
    fig, ax = new_fig(left=0.19, bottom=0.21, right=0.86)
    rng = np.random.default_rng(309)
    days, hours = 28, 24
    M = np.array([[420 + 180 * np.exp(-((h - 10) / 3.2) ** 2)
                   + 210 * np.exp(-((h - 17) / 2.9) ** 2) + rng.normal(0, 14)
                   for h in range(hours)] for _ in range(days)])
    M[8:11, 1:8] = np.nan
    M[19, 12:20] = np.nan
    cmap = CMAP_SEQ.copy()
    cmap.set_bad("white")
    im = ax.imshow(np.ma.masked_invalid(M), aspect="auto", cmap=cmap,
                   origin="lower", extent=[0, 24, 0.5, days + 0.5])
    ax.set_xticks([0, 4, 8, 12, 16, 20, 24])
    ax.set_yticks([1, 7, 14, 21, 28])
    ax.set_xlabel("时刻 " + IT["t"] + " / h")
    ax.set_ylabel("日序 " + IT["t"] + " / d")
    cbar(fig, ax, im, "负荷 " + IT["P"] + " / kW")
    save(fig, "03_热图", "03_热图_缺失数据缺口")

def ht_10():
    """温度场梯度:方向与陡缓由色带疏密给出。

    等值热图把二维场的梯度变成可看的图案:色带越密说明温度变化越陡,
    色带走向就是等温线方向。中心高热区与外缘的过渡带并不对称,右侧更陡,
    提示散热条件在那一侧更差。
    """
    fig, ax = new_fig(left=0.19, bottom=0.21, right=0.86)
    x = np.linspace(0, 1.2, 160)
    y = np.linspace(0, 1.0, 140)
    X, Y = np.meshgrid(x, y)
    Z = 32 + 30 * np.exp(-(((X - 0.62) / 0.26) ** 2 + ((Y - 0.52) / 0.30) ** 2)) \
        + 8 * np.exp(-(((X - 0.25) / 0.18) ** 2 + ((Y - 0.30) / 0.20) ** 2)) + 3 * Y
    im = ax.imshow(Z, aspect="auto", cmap=CMAP_SEQ, origin="lower",
                   extent=[0, 1.2, 0, 1.0])
    ax.set_xticks([0, 0.3, 0.6, 0.9, 1.2])
    ax.set_yticks([0, 0.25, 0.5, 0.75, 1.0])
    ax.set_xlabel("柜宽 " + IT["x"] + " / m")
    ax.set_ylabel("柜高 " + IT["y"] + " / m")
    cbar(fig, ax, im, "温度 " + IT["T"] + " / °C")
    save(fig, "03_热图", "03_热图_温度场梯度")
posted on 2026-11-08 00:12  小青蛙电气  阅读(3)  评论(0)    收藏  举报