- 数据可视化
【免费下载链接】vega
A visualization grammar.
导读
vega-transforms是 Vega 可视化语法生态中负责数据加工的核心包,为 Vega 数据流(dataflow)提供聚合、分箱、过滤、投影、排序、窗口计算等 22 个公开数据变换与 16 个内部基础设施变换。本文以 packages/vega-transforms/README.md 为骨架,结合源码与测试深入剖析每个变换的参数语义、增量更新原理与典型用法,帮助读者理解 Vega 从原始数据到可视化输入之间"数据管线"是如何构建的。
一、包定位:数据流中的"数据加工车间"
Vega 采用数据流(dataflow)架构:数据以 tuple 流的形式在变换算子(operator)之间传递,每个变换消费上游 pulse、产出新的 pulse。vega-transforms正是这一架构下所有数据加工算子的集合,其 package.json 将自身描述为 "Data processing transforms for Vega dataflows"。
从依赖关系可以清晰地看出它在整个 Vega 生态中的位置:
vega-dataflow:提供Transform基类、pulse、changeset等数据流基础设施;vega-statistics:提供bin(分箱函数)等统计原语,被 Bin、KDE、Quantile 等变换复用;vega-time:提供时间单位切分能力,支撑 TimeUnit 变换;vega-util:提供inherits、accessor、field、error等工具函数;d3-array:提供bisector等二分查找工具,被 Window 等变换用于区间调整。
packages/vega-transforms/index.js 统一导出了全部变换,例如aggregate、bin、collect、window、filter、lookup等,与源码目录 packages/vega-transforms/src 一一对应。上层vega-parser会把 Vega 规范(spec)中的data.transform数组解析并实例化为这些变换算子。
二、公开变换全景:22 个数据变换
README 将包提供的变换分为两层:公开(public)数据变换与内部(internal)变换。先看公开层,它们可直接在 Vega 规范的transform数组中使用:
| 变换 | 作用概述 | 源码 |
|---|---|---|
| Aggregate | 分组聚合,计算计数、求和、均值等统计量 | Aggregate.js |
| Bin | 数值分箱,离散化连续数据 | Bin.js |
| Collect | 收集全部 tuple,可选排序 | Collect.js |
| CountPattern | 统计文本字段中的词频/模式 | CountPattern.js |
| Cross | 计算数据对象的笛卡尔积 | Cross.js |
| Density | 一维核密度估计 | Density.js |
| DotBin | 点分箱,为 beeswarm 图等生成抖动的离散坐标 | DotBin.js |
| Extent | 计算字段的最小值与最大值 | Extent.js |
| Filter | 按谓词表达式过滤 tuple | Filter.js |
| Flatten | 将数组字段展开为多行 | Flatten.js |
| Fold | 宽表转长表,将多个字段折叠为键值对 | Fold.js |
| Formula | 计算表达式并写入新字段 | Formula.js |
| Impute | 对分组数据缺失值进行插补 | Impute.js |
| JoinAggregate | 聚合结果回填到每一行(不改变行数) | JoinAggregate.js |
| KDE | 一维/二维核密度估计 | KDE.js |
| Lookup | 基于键值做表连接,扩充字段 | Lookup.js |
| Pivot | 长表转宽表(透视) | Pivot.js |
| Project | 字段投影/重命名/删除 | Project.js |
| Quantile | 计算分位数 | Quantile.js |
| Sample | 随机采样数据子集 | Sample.js |
| Sequence | 生成等差数列/日期序列 | Sequence.js |
| TimeUnit | 将时间戳规整到指定时间单位 | TimeUnit.js |
| Window | 窗口计算(排名、滑动聚合、前后行引用) | Window.js |
这些变换覆盖了数据加工的主要环节:过滤清洗(Filter)、字段塑形(Project、Flatten、Fold、Pivot)、派生计算(Formula、Sequence、Bin)、统计汇总(Aggregate、JoinAggregate、Quantile、KDE、Density、Extent)、连接与查找(Lookup、Cross)、排序与窗口(Collect、Window)以及降采样(Sample、DotBin)。
说明:README 中每个变换附带的文档与源码链接均为外部地址,对应内容已在本仓库文档中,例如 aggregate 文档、bin 文档、window 文档,可在 docs/docs/transforms 下查阅全部 41 个变换的详细参数说明与示例。
三、核心变换深入:Aggregate、Bin、Window 源码级解析
3.1 Aggregate:分组聚合与增量维护
Aggregate 是使用频率最高的变换之一。它的参数模型定义在 Aggregate.js 的Definition中,metadata声明为{'generates': true, 'changes': true},意味着它会生成新 tuple 并产生增删改三种变更:
| 参数 | 类型 | 默认值 | 语义 |
|---|---|---|---|
groupby | Field[] | — | 分组字段,不指定则全部数据为一组 |
ops | enum[] | ['count'] | 聚合操作,如sum、mean、median |
aggregate_params | number[] | — | 聚合操作的参数(如exponential的衰减因子) |
fields | Field[] | — | 参与聚合的字段,与ops、as对齐 |
as | string[] | 自动生成 | 输出字段名,缺省形如sum_field |
drop | boolean | true | 是否丢弃空(零计数)分组 |
cross | boolean | false | 是否生成 groupby 值的全笛卡尔积(含空组) |
key | Field | — | 可选单键字段,加速分组键计算 |
从 Aggregate.js 的add/rem实现可以看出其增量维护策略:每个分组是一个cell,内部维护num(计数)与一组"聚合单子"(monoid);tuple 增删只更新对应 cell,无需全量重算。输出字段名由 util/AggregateOps.js 的measureName(op, field, as)生成:显式指定as用之,否则拼为op_field。
聚合操作全集定义在 util/AggregateOps.js,每个操作实现init/add/rem/value四个钩子,支持values、count、missing、valid、sum、product、mean、average、variance、variancep、stdev、stdevp、stderr、distinct、ci0、ci1、median、q1、q3、min、max、argmin、argmax、exponential、exponentialb。其中部分操作存在依赖关系(如stdev依赖variance,median依赖values),由resolve自动补齐依赖并排序(见 AggregateOps.js)。
典型用法(详见 aggregate 文档):
{ "type": "aggregate", "fields": ["foo", "bar", "bar"], "ops": ["valid", "sum", "median"], "as": ["v", "s", "m"] }分组键的生成逻辑在 util/AggregateKeys.js 中:单字段直接作为键访问器;多字段通过multikey用|拼接;无 groupby 时返回恒定空串(即全部数据为一组)。
3.2 Bin:数值分箱与浮点容差
Bin 将连续数值离散化为分箱区间,其参数定义于 Bin.js:
| 参数 | 类型 | 默认值 | 语义 |
|---|---|---|---|
field | Field | —(必填) | 待分箱字段 |
interval | boolean | true | 是否输出[bin0, bin1]区间两个边界 |
anchor | number | — | 锚定起点,使箱边界对齐该值 |
maxbins | number | 20 | 最大箱数 |
base | number | 10 | 底数(对数分箱用) |
divide | number[] | [5, 2] | 箱大小拆分因子 |
extent | number[2] | —(必填) | 数据范围[min, max] |
span | number | — | 覆盖范围,替代 extent 之差 |
step | number | — | 固定箱宽 |
steps | number[] | — | 显式箱边界序列 |
minstep | number | 0 | 最小箱宽 |
nice | boolean | true | 是否将边界规整为"好看"的数值 |
name | string | — | 生成的访问器名称 |
as | string[2] | ['bin0', 'bin1'] | 输出字段名 |
实现上有两个值得注意的细节(Bin.js):
- 区间右边界用"迂回算式":
t[b1] = start + step * (1 + (v - start) / step),注释明确说明这是为了改善浮点一致性(对应 vega/vega#830),同时保证无穷值可以穿透传播(vega/vega#2227)。 - epsilon 偏差:文件顶部定义了
const EPSILON = 1e-14,在计算箱下标时加上的微小偏差用于抵消浮点误差(对应 issue #1737)。
分箱函数本身复用vega-statistics的bin()生成,并在外层封装越界处理:小于start的归入-Infinity,大于stop的归入+Infinity。
3.3 Window:排名与滑动窗口计算
Window 把数据按groupby分组、按sort排序,然后在每个分区内执行窗口计算,结果写回原 tuple(metadata: {'modifies': true})。参数定义见 Window.js:
| 参数 | 类型 | 默认值 | 语义 |
|---|---|---|---|
sort | compare | — | 窗口内排序比较器 |
groupby | Field[] | — | 分区字段 |
ops | enum[] | — | 窗口操作或聚合操作 |
params | number[] | — | 窗口操作参数(如lag的偏移量) |
aggregate_params | number[] | — | 聚合操作参数 |
fields | Field[] | — | 输入字段 |
as | string[] | — | 输出字段名 |
frame | number[2] | [null, 0] | 窗口帧,[起始偏移, 结束偏移],null表示无界 |
ignorePeers | boolean | false | 是否忽略排序值相同的"并列行"对窗口边界的影响 |
窗口操作全集在 util/WindowOps.js:排名类row_number、rank、dense_rank、percent_rank、cume_dist、ntile;前后行引用类lag、lead、first_value、last_value、nth_value、prev_value、next_value。从实现看:
rank与dense_rank的区别在于rank按行号跳号(1, 1, 3),dense_rank连续编号(1, 1, 2);lag/lead通过w.index ± offset访问已排序数组,越界返回null;ntile基于cume_dist取Math.ceil(num * cume),要求num > 0;frame的语义为含起始、排他的结束:f[0]为带符号起始偏移(null视为 0),f[1]为结束偏移(null视为数据末尾,内部按i + f[1] + 1转为排他上界),并通过clamp限定在[0, n]。
窗口帧的类型细节值得注意(Window.js):若sort存在且ignorePeers为假,adjustRange会用bisector将窗口边界扩展到排序值相同的"并列行",从而保证rank、cume_dist等语义正确。这一点在 window 测试 中通过同时覆盖count/sum/min/max与row_number/rank/dense_rank/percent_rank/cume_dist/ntile/lag/lead/first_value/last_value/nth_value的混合用例得到验证。
四、其余常用变换速览
4.1 Filter:增量谓词过滤
Filter.js 定义只有一个必填参数expr(表达式访问器)。它的增量实现非常精巧:用一个fastmap缓存"被过滤掉"的 tuple id,ADD时谓词为真则放行、为假则入缓存;MOD时通过revisit比较缓存状态与当前谓词结果,在add/rem/mod三个队列间精确切换,并在缓存过大时通过df.runAfter(cache.clean)延迟清理(见 Filter.js)。
4.2 Formula:表达式派生字段
Formula.js 提供expr(表达式)与as(输出字段名),配合initonly参数控制是否只在初始化时计算。它是 Vega 规范中formula变换的底层实现,常用于在管道中生成衍生字段(如数值转换、字符串拼接)。
4.3 Lookup:键值连接扩充字段
Lookup.js 的参数模型定义了嵌套的index参数(from指定数据源、key指定索引键),以及values(要带出的字段)、fields(匹配字段,必填)、as(输出名)、default(未命中时的默认值)。从 Lookup.js 的校验逻辑看,多字段查找但未显式给出as时会直接报错('Multi-field lookup requires explicit "as" parameter.'),这保证了输出字段名的确定性。
4.4 Collect:排序与收集
Collect.js 只有一个sort参数,内部借助SortedList维护有序 tuple 列表,并用stableCompare保证同键元素保持输入顺序;同时负责向上游传播树形数据的root引用(Collect.js),这对层次布局(如 tree/treemap 变换)是必要的。
五、内部变换:数据流管道的"骨架"
README 同时列出了 16 个内部变换,它们不直接出现在用户规范中,而是由vega-parser在编译阶段自动插入,支撑数据流的核心机制:
| 内部变换 | 职责 |
|---|---|
| Compare | 生成稳定比较器 |
| Expression | 将表达式源码编译为可执行函数 |
| Facet / PreFacet / Subflow | 数据切分与子数据流管理,支撑嵌套/分面可视化 |
| Field | 字段访问器 |
| Generate | 程序化生成 tuple(配合 Sequence 等) |
| Key | 分组键计算 |
| MultiExtent / MultiValues | 合并多个来源的 extent / 值集合 |
| Params | 参数注入 |
| Proxy | 代理转发,处理异步数据源时序 |
| Relay | 多输入汇聚转发 |
| Sieve | 输出裁剪,防止重复传播 |
| TupleIndex | 按字段建立 tuple 索引(Lookup 的数据基础) |
| Values | 收集全部值集合 |
这些内部变换与公开变换共同组成了数据流图的"管道骨架":例如vega-parser在解析带groupby的 spec 时,会插入 Facet 系列算子;Lookup 变换所需的索引正是由tupleindex内部算子维护。
六、工程实践:如何查看、测试与使用
6.1 代码组织
- 公开与内部变换的源码统一位于 packages/vega-transforms/src,公共工具在 src/util(含
AggregateOps.js、AggregateKeys.js、WindowOps.js、WindowState.js、SortedList.js、TupleStore.js等); - 包的构建采用 Rollup(packages/vega-transforms/rollup.config.js),
package.json的exports指向构建产物; - 每个变换以
Transform.Definition静态属性声明参数 schema,这是 Vega 规范校验(vega-schema)与解析器的共同依据。
6.2 测试体系
packages/vega-transforms/test 下提供了与变换一一对应的测试文件(如aggregate-test.js、bin-test.js、filter-test.js、window-test.js、lookup-test.js、pivot-test.js等 30 个文件)。测试通过tape驱动,直接构造Dataflow实例、df.add(Transform)挂载算子、以changeset注入数据并断言输出,是学习每个变换参数语义与增量行为的最佳范例。例如 window-test.js 展示了如何在同一算子中组合sort、frame、ignorePeers与 15 种窗口操作。
运行测试:
cd packages/vega-transforms npm test6.3 集成方式
vega-transforms是vega主包(packages/vega/index.js)的组成部分,最终被打包进vega全量 bundle。在 Node 环境或构建工具中亦可单独引用:
import {aggregate, bin, collect, window} from 'vega-transforms';配合 vega-dataflow 的Dataflow、changesetAPI,即可脱离完整 Vega 运行时构建自定义的数据加工管线。
七、总结
vega-transforms以统一的Transform子类模式实现了 38 个算子(22 个公开 + 16 个内部),每个算子通过Definition声明参数 schema、以transform(_, pulse)处理增量变更。其核心设计可归纳为三点:
- 增量更新:Aggregate、Filter、Window 等算子均按
ADD/REM/MOD队列最小化重算,适合交互式场景下的流式数据更新; - 单子化聚合:聚合操作以
init/add/rem/value四钩子实现,可组合、可依赖解析、可增量维护; - 内外分层:公开变换面向用户规范,内部变换承载分面、索引、参数注入等管道机制,两者共同支撑 Vega 声明式数据管线的表达能力。
如需进一步掌握每个变换的完整参数表与示例,可直接查阅本仓库的 transform 文档目录(共 41 篇),并结合对应源码与测试文件交叉阅读。
- 数据可视化
【免费下载链接】vega
A visualization grammar.
相关推荐
Vega 回归变换深入指南:vega-regression 包的 Regression 与 Loess 数据变换
Vega 回归变换深入指南:vega regression 包的 Regression 与 Loess 数据变换 vega regression 是 Vega
数据可视化Vega Transforms 完全指南:数据流处理、变换分类与自定义扩展
Vega Transforms 完全指南:数据流处理、变换分类与自定义扩展 Vega 的 transforms (变换)是驱动数据可视化的核心处理引擎:它们在数
数据可视化Vega/Altair 数据转换器深度解析:优化可视化数据处理流程
Vega/Altair 数据转换器深度解析:优化可视化数据处理流程 引言:数据可视化的性能挑战 在数据可视化项目中,数据处理往往是性能瓶颈的关键所在。当面对大规
数据可视化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考