Altair Stack Transform 详解:用 transform_stack 显式计算与绘制堆叠值
【免费下载链接】altairDeclarative visualization library for Python项目地址: https://gitcode.com/gh_mirrors/al/altair
导读
本文聚焦 Altair(Python 声明式可视化库)中的Stack Transform(堆叠变换),讲解如何通过Chart.transform_stack()显式计算与堆叠编码(stacked encoding)相关联的值。你将掌握StackTransform的全部配置项(stack、as_、groupby、sort、offset)及底层调用机制,并能够把"隐式堆叠"的图表改写为"显式计算堆叠值"的图表,从而直接访问堆叠区间的起点与终点,为自定义堆叠样式、堆叠图元复用与复合图(如马赛克图)打下基础。
什么是 Stack Transform
堆叠图(stacked chart)是可视化中非常常见的形态——例如堆叠柱状图、堆叠面积图。在 Altair 中,当你直接使用mark_bar()、mark_area()等标记并配合color等编码时,Vega-Lite 会自动对数据进行分组(group)和堆叠(stack)。
下面是一个典型的隐式堆叠柱状图,使用altair.datasets中内置的barley数据集,按year分列、按variety分行,并用site着色:
import altair as alt from altair.datasets import data source = data.barley() alt.Chart(source).mark_bar().encode( column='year:O', x='yield:Q', y='variety:N', color='site:N' ).properties(width=220)这里的数据在内部已经被隐式地分组并堆叠。但问题来了:如果你需要直接访问这些堆叠后的值(例如某个品种在某年、某地块的累计区间),该怎么办?此时就需要 Stack Transform。
用 transform_stack 手动构造同一张图
我们可以用 Stack Transform 手动构造完全相同的堆叠柱状图,并把堆叠区间的起止值显式地计算出来:
import altair as alt from altair.datasets import data source = data.barley() alt.Chart(source).transform_stack( stack='yield', as_=['yield_1', 'yield_2'], groupby=['year', 'variety'], sort=[alt.SortField('site', 'descending')] ).mark_bar().encode( column='year:O', x=alt.X('yield_1:Q').title('yield'), x2='yield_2:Q', y='variety:N', color='site:N', tooltip=['site', 'yield', 'variety'] ).properties(width=220)对比两段代码可以清楚地看到差异:
- 隐式版本中,
x='yield:Q'由编码系统自动完成堆叠,我们看不到任何中间值; - 显式版本中,
transform_stack把yield字段按groupby=['year', 'variety']分组后计算堆叠,并将每根柱子的起点写入yield_1、终点写入yield_2; - 随后在编码层用
x='yield_1:Q'与x2='yield_2:Q'把柱子显式地绘制在这两个计算出的值之间——这正是文档中强调的"bars are now explicitly drawn between values computed and specified within the x and x2 encodings"(柱体现在显式绘制在x与x2编码所指定、计算出的值之间)。
sort=[alt.SortField('site', 'descending')]控制堆叠层(leaves)在栈中的排列顺序,保证与隐式堆叠版本的分层次序一致。
Transform Options:StackTransform 的完整配置项
transform_stack方法底层构建的是StackTransform类(定义于 altair/vegalite/v6/schema/core.py#L26729-L26765),其完整选项如下:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
stack | str/FieldName | 是 | 要被堆叠的字段。 |
as_ | str/FieldName/Sequence[str] | 是 | 输出字段名。可以是单个字符串,也可以是含两个元素的字符串数组,分别表示堆叠起点(stack start)与堆叠终点(stack end)的字段名。若只提供一个字符串(例如"val"),终点字段会自动命名为"val_end"。 |
groupby | Sequence[str]/FieldName | 是 | 分组字段,决定堆叠在哪些分组内独立进行。 |
offset | Literal['zero', 'center', 'normalize'] | 否 | 堆叠模式,默认"zero"。 |
sort | Sequence[SortField] | 否 | 决定堆叠层在栈中排列顺序的字段。 |
其中offset的三种模式(见 schema 定义 altair/vegalite/v6/schema/vega-lite-schema.json#L28009-L28016 与 core.py#L26739-L26745):
"zero"(默认):堆叠从0开始累积,即最常见的绝对堆叠效果;"center":将堆叠整体居中(以总和为中心向两侧展开),常用于河流图(streamgraph)等居中对称的堆叠面积图;"normalize":将每个堆叠点计算为百分比,输出值范围在[0, 1]之间,即百分比堆叠(100% stacked)效果。
注意:在transform_stack()方法签名中,由于as是 Python 关键字,该参数被命名为as_(见 altair/vegalite/v6/api.py#L3579-L3619),方法内部会通过**{"as": as_}将其转换为 Vega-Lite schema 中的as字段。
底层实现:方法如何落到 schema 上
transform_stack是顶层Chart对象的变换方法之一。从源码看(altair/vegalite/v6/api.py#L3615-L3619),它的实现非常简洁:
return self._add_transform( core.StackTransform( stack=stack, groupby=groupby, offset=offset, sort=sort, **{"as": as_} ) )关键点在于:
_add_transform追加机制:每次调用都会在 chart 的transform列表末尾追加一个新的StackTransform对象,因此transform_stack可以与其他变换(transform_aggregate、transform_window、transform_calculate等)自由链式组合;- schema 直通:
StackTransform的_schema = {"$ref": "#/definitions/StackTransform"},直接引用 Vega-Lite 的 schema 定义,最终会被序列化为 chart spec 中的"transform"数组项; - 排序与偏移:
sort字段的每一项是SortField(包含field与order),用于控制堆叠叶子的顺序;offset的类型是StackOffset_T,即上面提到的三值枚举。
测试用例 tests/vegalite/v6/test_api.py#L1234-L1243 验证了这一映射关系:
chart = alt.Chart().transform_stack("stacked", "x", groupby=["y"]) assert chart.transform == [ alt.StackTransform( groupby=["y"], stack="x", offset=Undefined, sort=Undefined, **{"as": "stacked"}, ) ]即:调用transform_stack("stacked", "x", groupby=["y"])后,chart 的transform列表精确等于一个StackTransform(groupby=["y"], stack="x", offset=Undefined, sort=Undefined, as="stacked"),未指定的offset与sort保持Undefined,由 Vega-Lite 引擎使用默认值(offset默认"zero")。
实战进阶:normalize 模式与多变换链式组合
Stack Transform 常常不是孤立使用的。仓库内置示例 tests/examples_arguments_syntax/mosaic_with_labels.py(马赛克图,mosaic chart)就展示了它的两个高阶用法:
base = ( alt.Chart(source) .transform_aggregate(count_="count()", groupby=["Origin", "Cylinders"]) .transform_stack( stack="count_", as_=["stack_count_Origin1", "stack_count_Origin2"], offset="normalize", sort=[alt.SortField("Origin", "ascending")], groupby=[], ) ... .transform_stack( stack="count_", groupby=["Origin"], as_=["y", "y2"], offset="normalize", sort=[alt.SortField("Cylinders", "ascending")], ) )从这段代码可以提炼出以下实战要点:
offset="normalize"配合as_=["stack_count_Origin1", "stack_count_Origin2"]输出[0, 1]区间的百分比堆叠值,用于计算每个格子在整图中的比例位置;groupby=[](空分组)表示在全局范围内堆叠,适用于需要整体归一化的场景;- 两次
transform_stack链式调用:第一次全局堆叠、第二次按Origin分组堆叠,再与transform_window、transform_calculate组合,最终用nx/nx2、ny/ny2等计算字段显式绘制矩形。这说明 Stack Transform 与聚合、窗口、计算变换组合后,可以构造出远超普通堆叠图的复杂图表。
常见问题与使用建议
- 什么时候该用 Stack Transform?当数据源不是 DataFrame,而是 URL 指向的 JSON/CSV 时,无法用 pandas 预处理,此时图内变换(包括
transform_stack)是唯一选择;当复合图中不同视图需要对同一数据做不同变换时,也应在 spec 内部完成堆叠。Altair 的官方建议(见 doc/user_guide/transform/index.rst)是:能用 pandas 处理就用 pandas(更灵活、更直观),图内变换用于数据源非 DataFrame 或复合图场景。 as_只传一个字符串会怎样?根据 schema 文档(vega-lite-schema.json#L28000),若提供单个字符串如"val",终点字段自动命名为"val_end",即实际输出val与val_end两个字段。- 绘制区间:显式堆叠后,通常用
x/x2(或y/y2)编码对来绘制标记区间,正如本文第一个显式示例中用yield_1与yield_2分别绑定x与x2。
小结
Stack Transform 是 Altair 数据变换体系中"把隐式行为显式化"的典型代表:它把编码层自动完成的堆叠计算下沉到transform阶段,输出可直接引用的起止字段,并支持zero、center、normalize三种偏移模式和SortField排序控制。通过transform_stack与transform_aggregate、transform_window、transform_calculate的链式组合,你可以在 spec 内部完成复杂的堆叠计算,进而构造马赛克图、百分比堆叠图等高级可视化。
【免费下载链接】altairDeclarative visualization library for Python项目地址: https://gitcode.com/gh_mirrors/al/altair
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考