资讯动态

用 Vega 绘制小提琴图(Violin Plot):KDE 核密度估计变换完整实战解析

发布时间:2026/9/24 10:59:29 来源:尧图企业网站定制
数据可视化【免费下载链接】vegaA visualization grammar.项目地址https://gitcode.com/gh_mirrors/ve/vega点击查看免费下载小提琴图violin plot把一组定量数据的分布可视化为概率密度函数的连续近似是比箱线图信息量更丰富的统计图表。本篇以 Vega 官方示例 violin-plot.vg.json 为骨架逐层拆解其数据管线、KDE 变换、聚合统计与多组标记编码并结合仓库源码vega-transforms、vega-statistics包说明带宽自动估计、曲线自适应采样等底层原理。读完后你将能独立复现并定制自己的小提琴图。Vega 小提琴图示例效果一、小提琴图要解决什么问题经典箱线图只给出中位数、四分位数与离群值的位置丢失了数据内部的多峰形态。小提琴图则通过核密度估计Kernel Density Estimation, KDE把每个分组的数据分布还原成平滑的密度曲线——曲线越宽说明该取值附近的样本越密集。示例 violin-plot.md 中明确指出密度曲线之外还用黑色线条额外标注了中位数median与四分位距interquartile range, IQR即 Q1–Q3因此比经典箱线图信息更丰富。示例选用的数据集是 docs/data/penguins.json共 3098 行记录字段包括Species阿德利企鹅 Adelie、帽带企鹅 Chinstrap、巴布亚企鹅 Gentoo 三种、Island、Beak Length (mm)、Body Mass (g)等。文章主题即围绕Body Mass (g)体重在三种物种上的分布展开。二、示例 Spec 全景从数据到渲染的四层结构完整示例文件位于 docs/examples/violin-plot.vg.json整体结构如下{ $schema: https://vega.github.io/schema/vega/v6.json, description: A violin plot example showing distributions for pengiun body mass., width: 500, padding: 5, config: { axisBand: { bandPosition: 1, tickExtra: true, tickOffset: 0 } }, signals: [ /* plotWidth、height、trim、bandwidth 四个信号 */ ], data: [ { name: penguins, url: data/penguins.json, transform: [/* filter */] }, { name: density, source: penguins, transform: [/* kde */] }, { name: stats, source: penguins, transform: [/* aggregate */] } ], scales: [ /* layout、xscale、hscale、color */ ], axes: [ /* 底部体重轴、左侧物种轴 */ ], marks: [ /* 一个 group 标记内含 area 两条 rect */ ] }整条管线可以概括为清洗数据 → KDE 生成密度曲线 → 聚合生成统计标注 → 分面编码渲染。下面逐层展开。三、数据准备过滤空值与缺失字段{ name: penguins, url: data/penguins.json, transform: [ { type: filter, expr: datum.Species ! null datum[Body Mass (g)] ! null } ] }原始数据中存在Species或Body Mass (g)为空的记录KDE 与聚合运算都要求完整数值因此先用filter变换剔除无效行。expr中datum[Body Mass (g)]的方括号写法是因为字段名含空格Vega 表达式引擎中必须用方括号访问这类字段。四、核心变换kde 生成概率密度曲线密度曲线的数据源是第二个数据集{ name: density, source: penguins, transform: [ { type: kde, field: Body Mass (g), groupby: [Species], bandwidth: {signal: bandwidth}, extent: {signal: trim ? null : [2000, 6500]} } ] }kde变换KDE.js对field指定的数值字段做一维核密度估计并按groupby分组输出每组均匀采样的密度点。默认输出字段为[value, density]KDE.Definition中as参数的默认值即每组产生一系列{Species, value, density}元组value是体重取值density是概率密度。4.1 参数速查表依据 docs/docs/transforms/kde.md 与 KDE.js 中的定义kde变换的完整参数如下参数类型默认值说明fieldField必填进行密度估计的数值字段groupbyField[]—分组字段不指定则全体数据作为一个组bandwidthNumber0高斯核宽度0表示用 Scott 方法自动估计extentNumber[]数据 [min, max]采样分布的[min, max]值域cumulativeBooleanfalsefalse输出密度pdftrue输出累积分布cdfcountsBooleanfalsefalse输出概率密度true输出平滑计数minstepsNumber25曲线采样最少点数maxstepsNumber200曲线采样最多点数stepsNumber—精确采样点数覆盖minsteps/maxstepsresolveStringindependentshared时所有组共享值域与步数便于堆叠asString[][value, density]输出字段名4.2 带宽自动估计Scotts method示例把bandwidth绑定到信号bandwidth默认值0即默认启用自动带宽。底层实现在 packages/vega-statistics/src/bandwidth.js注释明确引用 Scott (1992)《Multivariate Density Estimation》const n array.length, d deviation(array, f), q quartiles(array, f), h (q[2] - q[0]) / 1.34, v Math.min(d, h) || d || Math.abs(q[0]) || 1; return 1.06 * v * Math.pow(n, -0.2);即带宽 ≈1.06 · min(标准差, IQR/1.34) · n^(-1/5)。这个值随样本量增大而缩小数据越多核越窄密度曲线越精细。带宽过小曲线锯齿化严重过大则过度平滑抹平分布形态——这也是示例提供带宽滑杆让读者实时观察的原因。在 packages/vega-statistics/src/kde.js 中bandwidth()方法在传入0/undefined时自动调用上述估计bandwidth(_) { if (!arguments.length) return bandwidth; bandwidth _; if (!bandwidth support) bandwidth estimateBandwidth(support); return dist; },概率密度计算则对每个采样点累加高斯核贡献后归一化pdf(x) { let y 0, i 0; for (; in; i) { y kernel.pdf((x - support[i]) / bandwidth); } return y / bandwidth / n; }4.3 曲线采样自适应细分密度曲线并非全值域均匀取点而是由 packages/vega-statistics/src/sampleCurve.js 完成自适应采样先在minsteps25个均匀网格点上采样再对相邻点中点递归细分当相邻段的角度变化小于 0.5°MIN_RADIANS 0.5 * π / 180或已达maxsteps200上限时停止细分。这样在密度陡峭变化的区域自动加密采样点平坦区域则保持稀疏以最少点数逼近真实曲线。4.4 extent 信号的妙用trim 开关示例中extent是动态信号表达式extent: {signal: trim ? null : [2000, 6500]}trim为true默认勾选时extent为null每组各自按数据 [min, max] 采样曲线被裁剪到实际数据范围取消勾选时固定值域[2000, 6500]覆盖所有物种的体重范围便于组间对比密度形状。bandwidth信号同理滑杆范围为 0–200、步长 10代表自动估计。五、统计标注aggregate 计算中位数与四分位数小提琴图上的黑色线条来自第三个数据集stats{ name: stats, source: penguins, transform: [ { type: aggregate, groupby: [Species], fields: [Body Mass (g), Body Mass (g), Body Mass (g)], ops: [q1, median, q3], as: [q1, median, q3] } ] }aggregate变换Aggregate.js按Species分组对同一字段Body Mass (g)依次执行q1、median、q3三个分位数操作分别输出为q1、median、q3。结果每物种一行例如{ Species: Adelie, q1: 3350, median: 3700, q3: 4000 }实现上Aggregate.js 的init方法把fields、ops、as三个数组按索引一一对应编译为聚合单子measure monoid并在add/rem阶段增量维护每个分组单元cell最终由changes输出新增、修改与删除的聚合元组。ops的合法取值来自util/AggregateOps.js的ValidAggregateOpsq1/median/q3均在其中。六、比例尺与坐标轴横置小提琴的布局scales: [ { name: layout, type: band, range: height, domain: {data: penguins, field: Species} }, { name: xscale, type: linear, range: width, round: true, domain: {data: penguins, field: Body Mass (g)}, domainMin: 2000, zero: false, nice: true }, { name: hscale, type: linear, range: [0, {signal: plotWidth}], domain: {data: density, field: density} }, { name: color, type: ordinal, domain: {data: penguins, field: Species}, range: category } ]四把比例尺各司其职layout带型band比例尺把三种物种映射到纵向条带每个物种占据一行xscale线性比例尺映射体重值域zero: false避免从 0 起算、nice: true让坐标轴刻度取整、domainMin: 2000保证左侧留白hscale把密度值映射到水平方向的半宽范围[0, plotWidth]plotWidth信号默认 60——即小提琴在水平方向最多展开 60 像素color序数比例尺用内置category配色区分三个物种。坐标轴部分axes: [ {orient: bottom, scale: xscale, zindex: 1}, {orient: left, scale: layout, tickCount: 5, zindex: 1} ]底部为体重轴左侧为物种轴。全局配置axisBand中的bandPosition: 1让带型刻度标记对齐条带末端tickExtra: true补充额外刻度tickOffset: 0去掉偏移保证物种标签恰好落在各自条带中心。七、标记层分面 group 三层子标记最外层的group标记用facet按Species把密度数据切成若干子组实现一行一个小提琴{ type: group, from: { facet: { data: density, name: violin, groupby: Species } }, encode: { enter: { yc: {scale: layout, field: Species, band: 0.5}, height: {signal: plotWidth}, width: {signal: width} } }, data: [ { name: summary, source: stats, transform: [ { type: filter, expr: datum.Species parent.Species } ] } ], marks: [ /* area 两条 rect */ ] }每个分面 group 通过ycband: 0.5居中于条带与height: plotWidth定位为一个 60 像素高的画板内部再声明一个局部数据集summary从全局stats过滤出parent.Species分面父级物种名对应的统计行。这是 Vega 分面作用域scoped data的典型用法。分面内部三个子标记1. 密度面积area——小提琴本体{ type: area, from: {data: violin}, encode: { enter: { fill: {scale: color, field: {parent: Species}} }, update: { x: {scale: xscale, field: value}, yc: {signal: plotWidth / 2}, height: {scale: hscale, field: density} } } }area以x为水平位置、height为纵向半高、yc为中轴线把密度曲线镜像成上下对称的提琴形状——这是横置小提琴的标准画法。填充色通过{parent: Species}取父级分面的物种值。2. IQR 黑条rect{ type: rect, from: {data: summary}, encode: { enter: { fill: {value: black}, height: {value: 2} }, update: { x: {scale: xscale, field: q1}, x2: {scale: xscale, field: q3}, yc: {signal: plotWidth / 2} } } }一条高 2 像素的黑线从q1横跨到q3位于小提琴垂直中点标注四分位距。3. 中位数竖线rect{ type: rect, from: {data: summary}, encode: { enter: { fill: {value: black}, width: {value: 2}, height: {value: 8} }, update: { x: {scale: xscale, field: median}, yc: {signal: plotWidth / 2} } } }一条高 8 像素的竖线落在median位置标注中位数。两条黑线一横一竖正是原文档所述median 与 interquartile range 以黑色线条标注的实现。八、交互设计两个信号让分布看得见signals: [ { name: plotWidth, value: 60 }, { name: height, update: (plotWidth 10) * 3 }, { name: trim, value: true, bind: {input: checkbox} }, { name: bandwidth, value: 0, bind: {input: range, min: 0, max: 200, step: 1} } ]trim复选框切换密度采样值域见 4.4 节bandwidth滑杆0–200 连续调节核带宽0即自动估计用于直观理解带宽对平滑度的影响plotWidth与height提琴半宽 60总图高(60 10) × 3 210为三行物种各留 70 像素。由于kde变换的参数是信号驱动的拖动滑杆会触发变换重算与重渲染——这正是 Vega 响应式数据流dataflow的体现KDE.js 中_.modified()判断参数是否变化变化则重新partition、重新估计密度并输出新采样。九、运行与验证在浏览器中打开docs/examples/violin-plot.vg.json或从 示例索引 找到 Violin Plot 页面即可交互运行也可以用仓库 packages/vega 的运行时加载 spec。仓库自带的测试 packages/vega-transforms/test/kde-test.js 与 packages/vega-statistics/test/kde-test.js 覆盖了 KDE 变换与密度估计的数值正确性可作为理解输出结构的参考。十、进阶定制方向基于上述骨架可以低成本扩展出更多形态计数缩放给kde增加counts: true让提琴面积反映样本量而非概率累积分布cumulative: true把密度曲线换成 CDF 曲线多图堆叠resolve: shared让多组密度共享值域与采样步数配合steps精确采样可实现堆叠密度图更换字段把field改为Beak Length (mm)、Flipper Length (mm)即可在同一个penguins.json数据上绘制其他特征的分布纵向小提琴把layout换成水平带型、hscale映射到height方向即可旋转 90°。更完整的kde变换参数说明可查阅 docs/docs/transforms/kde.mdaggregate变换见 docs/docs/transforms/aggregate.md。对照 violin-plot.vg.json 逐行修改参数是掌握 Vega 统计可视化最快的方式。赞分享数据可视化【免费下载链接】vegaA visualization grammar.项目地址https://gitcode.com/gh_mirrors/ve/vega点击查看免费下载相关推荐Plotly.py 小提琴图Violin Plot完全指南从 px.violin 到 go.Violin 的统计分布可视化实战Plotly.py 小提琴图Violin Plot完全指南从 px.violin 到 go.Violin 的统计分布可视化实战 小提琴图Violin P数据可视化数据分析Vega KDE 变换Transform完全指南一维核密度估计、分组与堆叠实践Vega KDE 变换Transform完全指南一维核密度估计、分组与堆叠实践 kde 是 Vega 可视化语法Vega Visualization G数据可视化Vega 示例深度解析用 DensityKDE变换绘制 U-District 美食分布岭线图Vega 示例深度解析用 DensityKDE变换绘制 U District 美食分布岭线图 本篇文章以 Vega 官方示例 U District Cui数据可视化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价