- 数据可视化
- 数据分析
【免费下载链接】plotly.py
The interactive graphing library for Python :sparkles:
doc/python/table-subplots.md演示了 Plotly.py 的一项典型实战能力:通过plotly.subplots.make_subplots的specs参数,在同一个画布中同时容纳go.Table表格 trace 与go.Scatter折线 trace,形成"顶部数据表格 + 下方趋势曲线"的数据看板式布局。读完本文,你将掌握make_subplots的specs网格声明、row/col定向添加 trace、shared_xaxes轴共享以及表格样式(header/cells)的完整配置方法,并能直接从仓库源码层面理解表格为何被归类为domain子图类型。
使用场景:为什么需要表格与图表混排
在实际的数据分析工作中,经常需要同时呈现"明细数据"与"宏观趋势":表格负责展示精确数值,折线图负责呈现时间序列走势。Plotly.py 的make_subplots允许把二者编排进同一个Figure,共享同一套时间轴,形成信息互补的复合视图。原文给出的示例即为一幅"比特币挖矿 180 天统计看板":顶部是包含日期、交易量、市场价、哈希率等多列明细的表格,下方两张子图分别绘制挖矿收入(Mining revenue)与哈希率(Hash rate)的时间序列曲线。
完整示例:一张 3 行混合子图
以下是关联文档 doc/python/table-subplots.md 中的完整示例,它可直接复制运行(需要pandas、re以及网络访问以下载公开数据集):
import plotly.graph_objects as go from plotly.subplots import make_subplots import pandas as pd import re df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/Mining-BTC-180.csv") for i, row in enumerate(df["Date"]): p = re.compile(" 00:00:00") datetime = p.split(df["Date"][i])[0] df.iloc[i, 1] = datetime fig = make_subplots( rows=3, cols=1, shared_xaxes=True, vertical_spacing=0.03, specs=[[{"type": "table"}], [{"type": "scatter"}], [{"type": "scatter"}]] ) fig.add_trace( go.Scatter( x=df["Date"], y=df["Mining-revenue-USD"], mode="lines", name="mining revenue" ), row=3, col=1 ) fig.add_trace( go.Scatter( x=df["Date"], y=df["Hash-rate"], mode="lines", name="hash-rate-TH/s" ), row=2, col=1 ) fig.add_trace( go.Table( header=dict( values=["Date", "Number<br>Transactions", "Output<br>Volume (BTC)", "Market<br>Price", "Hash<br>Rate", "Cost per<br>trans-USD", "Mining<br>Revenue-USD", "Trasaction<br>fees-BTC"], font=dict(size=10), align="left" ), cells=dict( values=[df[k].tolist() for k in df.columns[1:]], align = "left") ), row=1, col=1 ) fig.update_layout( height=800, showlegend=False, title_text="Bitcoin mining stats for 180 days", ) fig.show()逐步拆解:从数据清洗到布局声明
1. 数据准备与日期清洗
数据集Mining-BTC-180.csv中的Date列带有00:00:00时间后缀。示例用正则表达式将其剥离,只保留日期部分,保证表格与折线图的时间标签干净一致:
p = re.compile(" 00:00:00") datetime = p.split(df["Date"][i])[0] df.iloc[i, 1] = datetime这一步对后续shared_xaxes=True的轴对齐至关重要——上下子图共享 x 轴时,刻度标签必须可比较。
2. specs:声明每个网格单元的子图类型
make_subplots的核心是specs:一个与网格一一对应的"列表的列表"。原文中rows=3, cols=1声明了 3 行 1 列网格,specs中每个元素声明该单元格的子图类型:
specs=[[{"type": "table"}], [{"type": "scatter"}], [{"type": "scatter"}]]{"type": "table"}:将第 1 行声明为表格子图;{"type": "scatter"}:将第 2、3 行声明为二维笛卡尔(xy)子图。
shared_xaxes=True让第 2、3 行散点子图共享 x 轴;vertical_spacing=0.03则把行间距压缩到 0.03(归一化坐标),给 800 像素高的画布留出尽可能多的表格显示空间。
3. add_trace:把 trace 定向填入指定网格单元
fig.add_trace(trace, row=..., col=...)是向子图填充内容的入口。原文将两个go.Scatter分别填入row=3与row=2,把go.Table填入row=1。注意表格子图与散点子图在填充方式上的差异:
- 散点 trace 通过
row/col关联到网格中对应的xaxis/yaxis; - 表格 trace 属于
domain子图类型,row/col参数决定其domain(绘图区域)坐标,详见下文原理分析。
4. 表格样式:header 与 cells
go.Table的展示样式集中在header与cells两个子对象中(可在 plotly/graph_objs/_table.py 的Table._valid_props中看到cells、header、domain、columnorder、columnwidth等全部有效属性):
go.Table( header=dict( values=["Date", "Number<br>Transactions", "Output<br>Volume (BTC)", ...], font=dict(size=10), align="left" ), cells=dict( values=[df[k].tolist() for k in df.columns[1:]], align="left" ) )header.values:表头文本列表,其中<br>是 Plotly 支持的换行标记,用于在窄列中把"Number Transactions"折成两行显示;header.font.size=10:缩小表头字号,让 8 列内容在有限宽度内容纳;cells.values:每个列表对应一列数据,示例用[df[k].tolist() for k in df.columns[1:]]把 DataFrame 中除第一列外的所有列批量转为列表;align="left":统一左对齐,原文同时在header与cells上设置。
最后通过fig.update_layout(height=800, showlegend=False, title_text=...)统一定制画布高度、图例开关与总标题。
深入原理:表格为何是 domain 子图类型
从源码看,make_subplots支持两类"占位"逻辑。在 plotly/_subplots.py 开头定义了子图类型常量:
_single_subplot_types = {"scene", "geo", "polar", "ternary", "map"} _subplot_types = set.union(_single_subplot_types, {"xy", "domain"})table、pie、parcoords、parcats等 trace 没有关联的坐标轴对象,而是通过自身的domain属性(x/y两个归一化区间)在画布上独立定位。_subplot_type_for_trace_type正是依据 trace 是否携带domain属性来判定:
trace = trace_validator.validate_coerce([{"type": trace_type}])[0] if "domain" in trace: return "domain" elif "xaxis" in trace and "yaxis" in trace: return "xy"也就是说,当specs中写{"type": "table"}时,make_subplots会把它归一化为domain子图类型,再由_init_subplot_domain生成子图引用:
def _init_subplot_domain(x_domain, y_domain): # No change to layout since domain traces are labeled individually subplot_ref = SubplotRef( subplot_type="domain", layout_keys=(), trace_kwargs={"domain": {"x": tuple(x_domain), "y": tuple(y_domain)}}, ) return (subplot_ref,)这段实现说明了两点:
- 不需要修改 layout:
layout_keys=()表示表格子图不创建任何xaxis/yaxis对象(散点子图则会创建xaxis/yaxis对,见同文件中SubplotXY命名元组); - 坐标通过 trace_kwargs 注入:子图网格计算出的
domain.x/domain.y会在添加 trace 时写入go.Table(domain=...),使表格精确占据网格中第 1 行对应的归一化区域。
相应地,make_subplots对specs的合法性有严格校验,例如specs维度必须与rows/cols完全匹配、colspan/rowspan不能越界,这些行为在 tests/test_core/test_subplots/test_make_subplots.py 中有系统性测试(如test_specs_underspecified、test_specs_colspan_too_big)。
make_subplots 关键参数速查(源自源码文档)
在 plotly/subplots.py 的make_subplots签名与 docstring 中,与本例直接相关的参数及默认值如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
rows/cols | 1/1 | 子图网格的行数/列数,必须大于 0 |
shared_xaxes | False | 布尔或字符串:True或'columns'表示同列子图共享 x 轴;'rows'表示同行共享;'all'表示全部共享 |
shared_yaxes | False | 同理控制 y 轴共享('columns'/True或'rows'/'all') |
horizontal_spacing | 0.2 / cols | 列间距(归一化坐标,0~1) |
vertical_spacing | 0.3 / rows | 行间距(归一化坐标,0~1) |
start_cell | 'top-left' | 网格编号起点,可选'bottom-left' |
print_grid | False | 为True时打印网格字符串表示,也可用结果Figure.print_grid() |
specs | None | 每个单元格的规格:type('xy'/'scene'/'polar'/'ternary'/'map'/'domain'/trace 类型)、secondary_y、colspan、rowspan、l/r/t/b内边距 |
subplot_titles | None | 每个子图的标题(按行优先顺序的列表,空字符串表示该格无标题) |
column_widths/row_heights | None | 各列宽度/各行高度比例 |
insets | None | 叠加在网格单元格上的内嵌子图规格 |
原文示例中vertical_spacing=0.03远小于默认的0.3 / 3 = 0.1,正是为了让表格区域获得更大展示高度;shared_xaxes=True则让两条折线共用同一时间轴,形成连贯的时间对比。
进阶调优:调试与排版
用 print_grid 验证网格结构
在make_subplots中传入print_grid=True,或在生成 figure 后调用fig.print_grid(),会在控制台打印类似下面的网格示意图,帮助确认每个单元格的类型与归属:
This is the format of your plot grid: [ (1, 1) table ] [ (2, 1) xy ] [ (3, 1) xy ]这是排查"trace 没有出现在预期位置"类问题的最快手段。
表格区域与图表区域的精细控制
- 若希望表格列宽更合理,可在
go.Table中设置columnwidth(以比例控制各列宽度)与columnorder(调整列渲染顺序),二者均为 plotly/graph_objs/_table.py 中Table声明的有效属性; - 若子图较多需要各自标题,使用
subplot_titles=["Table", "Revenue", "Hash rate"]; - 若希望网格中某些单元格留空,可将
specs对应位置设为None(例如在 3 列布局中实现[{}, None, {}]的效果)。
参考与延伸阅读
- 本文主题对应的原始文档:doc/python/table-subplots.md;
- 纯表格场景的完整用法:doc/python/table.md;
- 子图网格的整体编排、
shared_xaxes与specs的通用说明:doc/python/subplots.md; make_subplots的完整参数文档与实现:plotly/subplots.py;- 子图类型判定与 domain 布局的底层实现:plotly/_subplots.py;
go.Tabletrace 的属性定义:plotly/graph_objs/_table.py;- specs 校验与网格初始化测试:tests/test_core/test_subplots/test_make_subplots.py。
结合以上内容,你可以用同样的specs机制自由组合table、scatter、bar、pie、heatmap等任意类型的子图,把"数据明细 + 可视化图表"整合到单张交互式画布中。
- 数据可视化
- 数据分析
【免费下载链接】plotly.py
The interactive graphing library for Python :sparkles:
相关推荐
iOS菜单动画新标杆:FlowingMenu如何彻底改变用户交互体验
iOS菜单动画新标杆:FlowingMenu如何彻底改变用户交互体验 想要为你的iOS应用添加令人惊艳的流畅菜单动画吗?FlowingMenu正是你需要的终极解
数据可视化数据分析OptiLLM推理技术深度解析:从Chain-of-Thought到Monte Carlo Tree Search的演进
OptiLLM推理技术深度解析:从Chain of Thought到Monte Carlo Tree Search的演进 OptiLLM作为一款优化的LLM推理
Cursor AI助手试用限制深度解析:从设备标识符机制到实战破解指南
Cursor AI助手试用限制深度解析:从设备标识符机制到实战破解指南 Cursor免费试用重置工具 为开发者提供了解决AI助手试用限制的专业解决方案。本文将从
开发工具CLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考