02 小提琴图 violinplot:10 个案例说明它能展示什么

这类图能直观展示什么

小提琴图把核密度曲线左右镜像成“小提琴”形状,宽度即该取值处的数据密度,中间叠一条中位线。可以理解为“箱线图 + 旋转 90° 的分布形状”。

它比箱线图多展示三件事:① 分布有几个峰(多峰说明存在两种运行模式或两个子群);② 形状是否偏斜、尾部有多厚(极端风险);③ 密度峰值落在哪(最常出现的取值区间)。代价是读者需要一点读图训练,正式论文里常与箱线图并用以求稳妥。

版式约定:多组对比用竖直小提琴(类别在 x 轴、数值在 y 轴);只有一组时改用水平小提琴,让数值轴回到 x 轴并带上真实刻度——单组图竖着放会白白浪费整个横轴,读者从 x 轴读不到任何数值。本册第 1、3、5 号案例为水平版式,其余 7 个为竖直版式。

  • 轮廓宽度 = 该取值处的数据密度,红线 = 中位数;形状为核密度估计,带宽会影响轮廓细节。

10 个案例

02-1 日负荷双峰:两个峰值在一个图形里同时显形

小提琴的宽度就是密度,腰部收窄处即两个峰之间的谷。箱线图只会给出一个中位数,完全看不出"早晚各一个高峰",而这张图一眼能看到 10 时与 17 时两个鼓包、14 时缩腰。

日负荷双峰:两个峰值在一个图形里同时显形

02-2 单峰与双峰对照:形状差异一目了然

左侧工业负荷只有午间一个峰,右侧居民负荷有早晚两个峰。两者中位数接近、箱线图几乎分不出来,但小提琴的轮廓完全不同——这就是"分布形状"这层信息的价值。

单峰与双峰对照:形状差异一目了然

02-3 光伏出力右偏:长尾向上、体量下沉

密度主体压在左侧低出力段、右端拉出细长尖尾,说明"多数时段出力不高,少数晴天冲到满发"。这种右偏意味着用平均值会高估典型出力,装机容量评估应看中位数附近。

光伏出力右偏:长尾向上、体量下沉

02-4 两方案效果重叠:小提琴交叠处即判别力不足的证据

两条轮廓在中位线附近大面积重合,说明方案 B 虽然中位数略高,但多数工况下与 A 难以区分,不能据此断定 B 更优;箱线图只看箱体重叠同样看不出"重叠有多深"。

两方案效果重叠:小提琴交叠处即判别力不足的证据

02-5 风速分布尾部:极端风速的风险厚度

尾部越厚、越长,意味着超过切出风速而停机、或冲击塔筒的概率越大。这里右侧尖尾一直拖到 26 m/s 以上,是叶片载荷校核必须关注的区间。

风速分布尾部:极端风速的风险厚度

02-6 三组密度峰值位置:最优工况区间直接读出

鼓包最宽处就是出现频率最高的转速。方案三的峰值右移且更集中,说明它在更高转速下仍能稳定运行,这正是"扩速运行"想要的效果;箱线图只能给出中位数,读不出峰值在哪。

三组密度峰值位置:最优工况区间直接读出

02-7 各月负荷宽度:波动性随季节变化

小提琴的胖瘦直接反映离散程度。7 月轮廓明显更宽、腰更粗,说明夏季负荷波动大;1 月细长,说明负荷平稳。这一层"形状随组变化"的信息在箱线图上只剩箱体高低。

各月负荷宽度:波动性随季节变化

02-8 弃风受限出力:顶部被"削平"的截断分布

轮廓在上限处突然平齐、不再收尖,说明数据被限幅了——这是弃风或限电的典型痕迹。若只统计平均值,会误以为机组能力不足,实际是调度约束把高出力部分切掉了。

弃风受限出力:顶部被"削平"的截断分布

02-9 机组分层:一个宽分布里藏着的两个子群

单看整体是一条粗胖的小提琴,看不出内部结构;分组后再画,才发现大容量机组与小容量机组各自是一个窄峰。分组依据选错,整体分布就会掩盖真实的分层。

机组分层:一个宽分布里藏着的两个子群

02-10 五条馈线同图:形状与位置同时比较

位置看中位线高低,形状看胖瘦与鼓包数量。L3 最胖(波动大)、L5 出现两个鼓包(存在两种运行模式),这两条结论在并排的小提琴上同时可得,换成五张直方图要来回翻页才能拼出来。

五条馈线同图:形状与位置同时比较

代码

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

# 摘自 code/小提琴图案例261107.py

def vi_01():
    """日负荷双峰:两个峰值在一个图形里同时显形。

    小提琴的宽度就是密度,腰部收窄处即两个峰之间的谷。箱线图只会给出一个中位数,
    完全看不出"早晚各一个高峰",而这张图一眼能看到 10 时与 17 时两个鼓包、14 时缩腰。
    """
    fig, ax = new_fig(left=0.25, bottom=0.19)
    rng = np.random.default_rng(201)
    d = np.concatenate([rng.normal(430, 52, 700), rng.normal(660, 58, 560)])
    violin(ax, [d], ["典型日负荷"], colors=[C5], orient="h")
    ax.set_xlabel("有功负荷 " + IT["P"] + " / kW")
    ax.set_xlim(215, 900)
    ax.set_ylim(0.4, 1.6)
    save(fig, "02_小提琴图", "02_小提琴图_日负荷双峰")

def vi_02():
    """单峰与双峰对照:形状差异一目了然。

    左侧工业负荷只有午间一个峰,右侧居民负荷有早晚两个峰。两者中位数接近、箱线图几乎分不出来,
    但小提琴的轮廓完全不同——这就是"分布形状"这层信息的价值。
    """
    fig, ax = new_fig()
    rng = np.random.default_rng(202)
    ind = rng.normal(520, 68, 1200)
    res = np.concatenate([rng.normal(360, 46, 600), rng.normal(620, 52, 600)])
    violin(ax, [ind, res], ["工业负荷", "居民负荷"], colors=[C5, C1])
    ax.set_xlabel("负荷类型")
    ax.set_ylabel("有功负荷 " + IT["P"] + " / kW")
    ax.set_ylim(180, 800)
    save(fig, "02_小提琴图", "02_小提琴图_单峰双峰对照")

def vi_03():
    """光伏出力右偏:长尾向上、体量下沉。

    密度主体压在左侧低出力段、右端拉出细长尖尾,说明"多数时段出力不高,少数晴天冲到满发"。
    这种右偏意味着用平均值会高估典型出力,装机容量评估应看中位数附近。
    """
    fig, ax = new_fig(left=0.25, bottom=0.19)
    rng = np.random.default_rng(203)
    pv = rng.gamma(2.6, 105, 1400)
    violin(ax, [pv], ["光伏出力"], colors=[C3], orient="h")
    ax.set_xlabel("有功出力 " + IT["P"] + " / kW")
    ax.set_xlim(-35, 930)
    ax.set_ylim(0.4, 1.6)
    save(fig, "02_小提琴图", "02_小提琴图_光伏出力右偏")

def vi_04():
    """两方案效果重叠:小提琴交叠处即判别力不足的证据。

    两条轮廓在中位线附近大面积重合,说明方案 B 虽然中位数略高,但多数工况下与 A 难以区分,
    不能据此断定 B 更优;箱线图只看箱体重叠同样看不出"重叠有多深"。
    """
    fig, ax = new_fig()
    rng = np.random.default_rng(204)
    a = rng.normal(0.872, 0.030, 900)
    b = rng.normal(0.889, 0.032, 900)
    violin(ax, [a, b], ["方案 A", "方案 B"], colors=[C5, C1])
    ax.set_xlabel("配置方案")
    ax.set_ylabel("系统综合效率 " + IT["eta"])
    ax.set_ylim(0.77, 0.99)
    save(fig, "02_小提琴图", "02_小提琴图_两方案效果重叠")

def vi_05():
    """风速分布尾部:极端风速的风险厚度。

    尾部越厚、越长,意味着超过切出风速而停机、或冲击塔筒的概率越大。
    这里右侧尖尾一直拖到 26 m/s 以上,是叶片载荷校核必须关注的区间。
    """
    fig, ax = new_fig(left=0.25, bottom=0.19)
    rng = np.random.default_rng(205)
    v = rng.weibull(2.0, 1600) * 9.6
    violin(ax, [v], ["轮毂风速"], colors=[C5], orient="h")
    ax.axvline(25, color=C2, lw=0.8, ls="--", label="切出风速 25 m/s")
    ax.set_xlabel("风速 " + IT["v"] + " / (m/s)")
    ax.set_xlim(-1.4, 35)
    ax.set_ylim(0.4, 1.6)
    legend(ax, "upper right", ncol=1)
    save(fig, "02_小提琴图", "02_小提琴图_风速尾部风险")

def vi_06():
    """三组密度峰值位置:最优工况区间直接读出。

    鼓包最宽处就是出现频率最高的转速。方案三的峰值右移且更集中,说明它在更高转速下仍能稳定运行,
    这正是"扩速运行"想要的效果;箱线图只能给出中位数,读不出峰值在哪。
    """
    fig, ax = new_fig()
    rng = np.random.default_rng(206)
    a = rng.normal(1480, 120, 800)
    b = rng.normal(1620, 105, 800)
    c = rng.normal(1810, 78, 800)
    violin(ax, [a, b, c], ["方案一", "方案二", "方案三"], colors=[C6, C5, C1])
    ax.set_xlabel("运行方案")
    ax.set_ylabel("运行转速 " + IT["n"] + " / (r/min)")
    ax.set_ylim(1150, 2150)
    save(fig, "02_小提琴图", "02_小提琴图_密度峰值位置")

def vi_07():
    """各月负荷宽度:波动性随季节变化。

    小提琴的胖瘦直接反映离散程度。7 月轮廓明显更宽、腰更粗,说明夏季负荷波动大;
    1 月细长,说明负荷平稳。这一层"形状随组变化"的信息在箱线图上只剩箱体高低。
    """
    fig, ax = new_fig()
    rng = np.random.default_rng(207)
    data = [rng.normal(470, 48, 700), rng.normal(560, 72, 700),
            rng.normal(690, 105, 700), rng.normal(520, 58, 700)]
    violin(ax, data, ["1 月", "4 月", "7 月", "10 月"],
           colors=[C5, C5, C1, C5])
    ax.set_xlabel("月份")
    ax.set_ylabel("有功负荷 " + IT["P"] + " / kW")
    ax.set_ylim(200, 1050)
    save(fig, "02_小提琴图", "02_小提琴图_各月波动宽度")

def vi_08():
    """弃风受限出力:顶部被"削平"的截断分布。

    轮廓在上限处突然平齐、不再收尖,说明数据被限幅了——这是弃风或限电的典型痕迹。
    若只统计平均值,会误以为机组能力不足,实际是调度约束把高出力部分切掉了。
    """
    fig, ax = new_fig()
    rng = np.random.default_rng(208)
    raw = rng.normal(720, 130, 1200)
    cut = np.clip(raw, 0, 800)
    violin(ax, [raw, cut], ["未受限", "受限后"], colors=[C5, C3])
    ax.axhline(800, color=C2, lw=0.8, ls="--", label="调度上限 800 kW")
    ax.set_xlabel("运行状态")
    ax.set_ylabel("有功出力 " + IT["P"] + " / kW")
    ax.set_ylim(200, 1150)
    legend(ax, "upper right", ncol=1)
    save(fig, "02_小提琴图", "02_小提琴图_弃风截断分布")

def vi_09():
    """机组分层:一个宽分布里藏着的两个子群。

    单看整体是一条粗胖的小提琴,看不出内部结构;分组后再画,才发现大容量机组与小容量机组
    各自是一个窄峰。分组依据选错,整体分布就会掩盖真实的分层。
    """
    fig, ax = new_fig()
    rng = np.random.default_rng(209)
    allg = np.concatenate([rng.normal(680, 60, 500), rng.normal(940, 55, 500)])
    small = rng.normal(680, 60, 500)
    large = rng.normal(940, 55, 500)
    violin(ax, [allg, small, large], ["合并统计", "小容量", "大容量"],
           colors=[C6, C5, C1])
    ax.set_xlabel("分组方式")
    ax.set_ylabel("机组出力 " + IT["P"] + " / kW")
    ax.set_ylim(500, 1150)
    save(fig, "02_小提琴图", "02_小提琴图_机组分层结构")

def vi_10():
    """五条馈线同图:形状与位置同时比较。

    位置看中位线高低,形状看胖瘦与鼓包数量。L3 最胖(波动大)、L5 出现两个鼓包(存在两种运行模式),
    这两条结论在并排的小提琴上同时可得,换成五张直方图要来回翻页才能拼出来。
    """
    fig, ax = new_fig()
    rng = np.random.default_rng(210)
    data = [rng.normal(290, 42, 700), rng.normal(360, 58, 700),
            rng.normal(410, 96, 700),
            np.concatenate([rng.normal(250, 30, 350), rng.normal(410, 34, 350)]),
            rng.normal(330, 48, 700)]
    violin(ax, data, ["L1", "L2", "L3", "L4", "L5"], colors=[C5] * 5)
    ax.set_xlabel("馈线")
    ax.set_ylabel("电流有效值 " + IT["I"] + " / A")
    ax.set_ylim(80, 680)
    save(fig, "02_小提琴图", "02_小提琴图_五馈线形状对比")
posted on 2026-11-08 00:12  小青蛙电气  阅读(2)  评论(0)    收藏  举报