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_小提琴图_五馈线形状对比")
浙公网安备 33010602011771号