公司动态

桑基图实战指南:从数据准备到Plotly高级可视化

📅 2026/8/15 3:51:27
桑基图实战指南:从数据准备到Plotly高级可视化
1. 项目概述从数据到洞察的“流量”可视化如果你手头有一堆数据想看看它们是怎么“流动”的——比如用户从哪个渠道来最终转化到了哪个产品或者一笔资金在几个部门间是如何流转分配的——这时候一张清晰的桑基图Sankey Diagram可能就是你的最佳选择。它不像普通的柱状图、折线图那样展示静态的数量或趋势而是专注于揭示流量、能量或资源的转移路径与比例关系。核心价值在于它能在一张图上同时告诉你“从哪里来”、“到哪里去”以及“每个环节的规模有多大”信息密度极高。我最初接触桑基图是在分析一个电商平台的用户行为路径时传统的漏斗图只能看流失但看不清流失的用户具体跳转去了哪里。而桑基图完美地展现了用户在不同页面间的穿梭轨迹那些隐藏的“回流”和“交叉访问”模式一目了然为优化页面设计和营销策略提供了直接依据。无论是能源管理、财务分析、用户行为研究还是生产流程优化只要你的数据描述了某种“流”桑基图就能帮你把故事讲清楚。绘制一张桑基图技术本身不复杂难点在于数据的结构化整理和视觉元素的审美与清晰度平衡。网上教程很多但照着做出来的图常常要么布局混乱要么颜色扎眼根本没法用在正式报告里。这篇文章我就结合自己多次踩坑和实战的经验从数据准备、工具选择、绘制技巧到美化导出手把手带你画出一张专业级的桑基图让你不仅能“画出来”更能“用得好”。2. 核心思路与工具选型为什么是它以及用什么画在动手写代码或点鼠标之前想清楚两个问题我的数据真的适合桑基图吗我该用什么工具来实现2.1 桑基图适用场景深度解析桑基图的核心是展示“流量”的守恒即所有起点的总“流量”等于所有终点的总“流量”中间过程可以分合。它特别适合以下几种场景路径分析这是最典型的应用。例如网站/APP的用户旅程展示用户从首页开始经过搜索、商品详情页、加入购物车、支付等环节的流转情况包括流失和跳转。能量与物质流在工业工程、能源领域展示能源如电力、热能或原材料在系统内各环节的转换、传输和损失情况。资金流与预算分配跟踪资金从不同来源如部门A、B流向不同项目或支出类别如研发、市场、人力的过程。选民或人口迁移展示选举中选民在不同政党间的流动或人口在不同区域间的迁徙模式。不适用的情况如果你的数据没有清晰的“源-目标”对应关系或者你只想比较几个独立类别的数值大小用柱状图或展示随时间的变化趋势用折线图那么桑基图可能不是最佳选择强行使用会导致图表难以解读。2.2 主流绘制工具横向对比与选型建议工具选择上没有绝对的最好只有最适合当前场景的。我主要从编程和非编程两个维度来对比。1. 编程类工具适合批量生成、集成到系统、高度定制Python Plotly这是我最推荐也是目前生态最完善、效果最炫酷的选择。Plotly的plotly.graph_objects.Sankey模块功能强大交互性极佳鼠标悬停显示数值、高亮连接路径并且可以轻松导出为HTML交互图表或静态图片。配合pandas进行数据处理流程非常顺畅。对于有一定Python基础的数据分析师或工程师来说学习成本适中回报很高。优势交互性强图表美观与Python数据分析栈无缝集成文档丰富。劣势需要编程环境。Python Pyecharts这是一个基于Echarts的Python库生成的桑基图同样具有优秀的交互性并且风格更接近国内常用的Echarts配置。如果你团队的技术栈偏向Web前端或习惯Echarts的配置项Pyecharts是个好选择。优势配置项灵活符合Echarts使用习惯交互性好。劣势社区活跃度和Plotly相比稍弱某些高级特性可能需要查中文文档。D3.js这是Web前端可视化的皇冠明珠能力最强定制自由度最高。你可以控制每一个像素的渲染。但与之对应的是极高的学习曲线需要熟悉JavaScript、SVG乃至数据绑定等概念。除非有极其特殊的定制化需求如与复杂Web应用深度集成或者你是专业的前端可视化工程师否则不建议初学者直接上手。优势无限定制性能可控可集成到任何Web页面。劣势学习曲线陡峭开发效率较低。2. 非编程/在线工具适合快速出图、演示、一次性分析RAWGraphs一个非常优秀的开源在线数据可视化工具。你只需要将结构化的数据如CSV粘贴或上传进去选择Sankey模板调整颜色和标签就能快速生成一张静态的、质量很高的桑基图。它极大地降低了技术门槛。优势无需编程快速简单支持多种数据格式可视化效果专业。劣势交互性有限主要是静态图复杂定制能力较弱。Microsoft Power BI / Tableau这些商业智能工具内置了桑基图或通过扩展插件支持。如果你所在的企业已经在使用这些BI平台进行数据分析那么直接利用现有生态是最方便的可以享受数据刷新、仪表板联动等特性。优势与企业数据流整合好支持动态更新和交互式仪表板。劣势需要软件许可自定义视觉样式可能受工具限制。在线生成器如SankeyMATIC等有一些专为桑基图设计的在线网站。它们通常有固定的输入格式例如源 [流量值] 目标上手极快。优势极度简单专一功能。劣势功能单一数据隐私性需考虑不适合处理复杂或敏感数据。我的选型心得 对于大多数数据分析场景尤其是需要重复生成、报告或轻度集成的Python Plotly组合是平衡了能力、效率和美观度的“甜点”。下文也将以这个组合为例进行详细演示。对于业务人员或快速原型制作RAWGraphs是绝佳的起点。3. 数据准备决定图表质量的“地基工程”数据格式不对再好的工具也画不出正确的桑基图。这是新手最容易栽跟头的地方。桑基图需要一种特定的“边列表”或“节点-连接”格式的数据。3.1 标准数据格式详解Plotly桑基图需要三个核心列表长度必须一致source: 源节点的索引列表。target: 目标节点的索引列表。value: 从源节点流向目标节点的流量值列表。此外还需要一个label列表按索引顺序存放所有唯一节点的名字。听起来抽象我们看一个具体的例子。假设我们分析一个简化版的用户购物流程100个用户从“首页”进入。其中60人进入“搜索页”40人直接离开流失。进入“搜索页”的60人中30人进入“商品页”30人离开。进入“商品页”的30人中20人进入“支付页”10人离开。“支付页”的20人全部完成购买进入“完成”节点。首先我们需要列出所有唯一的节点并给它们编号索引从0开始labels [‘首页’, ‘搜索页’, ‘商品页’, ‘支付页’, ‘完成’, ‘流失’]索引对应为0:首页, 1:搜索页, 2:商品页, 3:支付页, 4:完成, 5:流失然后我们用source,target,value来描述每一条流流1首页(0) - 搜索页(1)流量 60流2首页(0) - 流失(5)流量 40流3搜索页(1) - 商品页(2)流量 30流4搜索页(1) - 流失(5)流量 30流5商品页(2) - 支付页(3)流量 20流6商品页(2) - 流失(5)流量 10流7支付页(3) - 完成(4)流量 20将其转化为三个列表source [0, 0, 1, 1, 2, 2, 3] target [1, 5, 2, 5, 3, 5, 4] value [60, 40, 30, 30, 20, 10, 20]这就是Plotly桑基图需要的数据结构。3.2 从原始数据到桑基图数据的转换实战你的原始数据很可能是一行行的事件日志或交易记录比如一个CSV文件每一行是一次用户访问有user_id,page_from,page_to等字段。我们需要将其聚合、转换成上述格式。这里用pandas演示一个常见的转换流程import pandas as pd # 假设你的原始数据是这样的DataFrame df pd.DataFrame({ ‘user_id’: [1, 1, 1, 2, 2, 3, 3, 3, 4, 4], ‘page_from’: [‘首页’, ‘首页’, ‘搜索页’, ‘首页’, ‘搜索页’, ‘首页’, ‘搜索页’, ‘商品页’, ‘首页’, ‘商品页’], ‘page_to’: [‘搜索页’, ‘流失’, ‘商品页’, ‘流失’, ‘流失’, ‘搜索页’, ‘商品页’, ‘支付页’, ‘搜索页’, ‘流失’] }) # 1. 聚合数据统计每条路径 (page_from - page_to) 出现的次数即用户数 flow_df df.groupby([‘page_from’, ‘page_to’]).size().reset_index(name‘value’) # 2. 获取所有唯一的节点并建立索引映射 all_nodes pd.unique(flow_df[[‘page_from’, ‘page_to’]].values.ravel(‘K’)) node_label list(all_nodes) node_index {node: i for i, node in enumerate(node_label)} # 创建节点名到索引的字典 # 3. 将节点名映射为索引生成source, target列表 source_indices flow_df[‘page_from’].map(node_index).tolist() target_indices flow_df[‘page_to’].map(node_index).tolist() values flow_df[‘value’].tolist() print(“labels:”, node_label) print(“source:”, source_indices) print(“target:”, target_indices) print(“value:”, values)运行这段代码你就能得到格式化好的labels,source,target,value直接可以喂给Plotly。注意确保你的数据中不包含循环引用例如 A-B, B-C, C-A这会导致桑基图布局混乱甚至无法渲染。通常桑基图用于有向无环图DAG结构的数据。4. 使用Plotly绘制基础桑基图数据准备好了画图就是几行代码的事。我们先画出最基础的版本。4.1 最小化实现代码与参数解读import plotly.graph_objects as go # 使用上一节准备好的数据 labels [‘首页’, ‘搜索页’, ‘商品页’, ‘支付页’, ‘完成’, ‘流失’] source [0, 0, 1, 1, 2, 2, 3] target [1, 5, 2, 5, 3, 5, 4] value [60, 40, 30, 30, 20, 10, 20] # 创建桑基图对象 fig go.Figure(data[go.Sankey( node dict( pad 15, # 节点之间的垂直间距 thickness 20, # 节点的粗细 line dict(color “black”, width 0.5), # 节点边框 label labels, # 节点标签 color “blue” # 节点颜色可以是一个颜色也可以是一个颜色列表 ), link dict( source source, # 源节点索引 target target, # 目标节点索引 value value # 流量值 ))]) # 更新布局设置标题和图形大小 fig.update_layout(title_text“基础用户购物流程桑基图”, font_size10, width800, height600) # 显示图形在Jupyter Notebook中 fig.show() # 如果想保存为HTML交互文件 # fig.write_html(“basic_sankey.html”) # 如果想保存为静态图片需要安装 kaleido # fig.write_image(“basic_sankey.png”)运行这段代码一个交互式的桑基图就会呈现出来。你可以鼠标悬停在任何节点或连接线上查看具体的流量数值。关键参数解析node.pad: 控制节点间的纵向距离。值越大节点排列越稀疏图也越高。node.thickness: 节点的宽度。流量大的节点会自动变“厚”这个参数设置了基础厚度。node.line: 设置节点边框的样式让节点轮廓更清晰。link: 除了source,target,value这三个必需项还可以设置color连接线的颜色、hoverinfo悬停信息等。4.2 交互性增强与悬停信息定制Plotly桑基图的默认交互已经很好但我们还可以让它更贴心。比如默认悬停只显示label和value我们可能想显示更多信息比如百分比。# 计算总流量通常是最左侧所有源节点的流量和或最右侧所有目标节点的流量和 total_value sum(value[:2]) # 这里首页流出的总流量是6040100 # 为连接线定制悬停文本我们想在悬停时显示流量值和占总流量的百分比 hover_texts [] for v in value: percentage (v / total_value) * 100 hover_texts.append(f“流量: {v}br占比: {percentage:.1f}%”) # br是HTML换行 fig go.Figure(data[go.Sankey( node dict( pad15, thickness20, linedict(width0.5), labellabels, color“lightblue” ), link dict( sourcesource, targettarget, valuevalue, customdatahover_texts, # 传入自定义数据 hovertemplate‘%{customdata}extra/extra’ # 定义悬停模板extra/extra用于隐藏默认的trace名 ))]) fig.update_layout(title_text“带百分比悬停的桑基图”, font_size10) fig.show()现在当你鼠标悬停在连接线上时会看到“流量: 60占比: 60.0%”这样的信息更加直观。5. 高级美化与布局优化技巧基础图表能看但离“专业”和“美观”还有距离。下面分享几个让桑基图脱颖而出的技巧。5.1 色彩策略用颜色讲故事杂乱的颜色是桑基图的大忌。好的配色方案能引导视线、区分维度、提升美感。1. 按节点类别着色这是最常用的方法。例如将所有“入口”节点如首页设为蓝色所有“中间过程”节点搜索页、商品页设为绿色所有“最终状态”节点完成、流失设为红色流失和深绿色完成。连接线颜色可以继承源节点的颜色通过link.color source_node_color这样整条流路径颜色一致便于追踪。# 定义每个节点的颜色 node_colors [“#1f77b4”, # 首页 - 蓝色 “#2ca02c”, # 搜索页 - 绿色 “#2ca02c”, # 商品页 - 绿色 “#2ca02c”, # 支付页 - 绿色 “#9467bd”, # 完成 - 紫色 “#d62728”] # 流失 - 红色 # 连接线颜色设置为源节点的颜色透明度可调 link_colors [node_colors[i] for i in source] # 根据source索引取色 fig go.Figure(data[go.Sankey( node dict(pad15, thickness20, linedict(width0.5), labellabels, colornode_colors), link dict(sourcesource, targettarget, valuevalue, colorlink_colors) # 应用颜色 )])2. 使用连续色系或渐变色如果你想用颜色表达流量大小不推荐和类别色混用会乱可以使用Plotly的连续色标比如Plasma、Viridis。但这通常用于连接线节点用类别色更好。实操心得颜色不要超过5-7种。优先使用色盲友好的配色方案如Set2,Set3。可以在Plotly的px.colors.qualitative模块下找到很多优秀的预设配色列表。5.2 布局调整与标签重叠解决当节点很多或名字很长时标签重叠会非常严重。Plotly提供了一些控制参数但效果有限。node.x和node.y: 这是大杀器。你可以手动指定每个节点在[0,1]坐标系中的位置。x代表水平方向通常0在最左1在最右y代表垂直方向0在最下1在最上。通过精细调整可以彻底解决重叠问题但比较耗时。# 示例手动排列节点位置 node_x [0, 0.2, 0.4, 0.6, 0.8, 0.8] # 首页在最左流失和完成在最右 node_y [0.1, 0.3, 0.5, 0.7, 0.9, 0.1] # 粗略分配垂直位置 fig go.Figure(data[go.Sankey( node dict(..., xnode_x, ynode_y), link ... )])node.align: 可以设置为’justify’,’left’,’right’,’center’影响多行标签的对齐方式。终极方案缩写或换行如果标签文字太长考虑使用缩写如“Search Page” - “SRH”或者在数据预处理时插入换行符\n例如“商品详情页”改为“商品\n详情页”。5.3 添加数据标签与注释有时我们希望在节点上直接显示流量值而不仅仅是悬停显示。# 计算每个节点的净流量流入-流出对于最左/最右的节点可以近似用总流入或总流出代替 # 这里我们简单地在节点标签后加上总流量对于源节点是流出和对于目标节点是流入和 # 这是一个简化计算严谨的做法需要分别计算每个节点的流入和流出总和。 node_labels_with_value [] for i, label in enumerate(labels): # 计算流入该节点的总流量 inflow sum(v for idx, v in zip(target, value) if idx i) # 计算流出该节点的总流量 outflow sum(v for idx, v in zip(source, value) if idx i) # 取较大的一个作为该节点的“代表性流量” rep_value max(inflow, outflow) if rep_value 0: node_labels_with_value.append(f“{label}\n({rep_value})”) else: node_labels_with_value.append(label) fig go.Figure(data[go.Sankey( node dict(..., labelnode_labels_with_value), # 使用带流量的新标签 link ... )])这样节点上就会显示如“首页 (100)”的标签信息量更足。6. 复杂场景与性能优化当数据量变大节点50连接100时你可能会遇到图表渲染慢、布局混乱的问题。6.1 处理大规模数据节点与连接过多数据聚合这是最重要的步骤。在生成source、target列表前对原始数据进行聚合过滤掉流量过小的连接。例如只保留占比前95%的流量路径将剩余的小流量合并为一个“其他”节点。# 假设flow_df是聚合后的DataFrame有‘source’, ‘target’, ‘value’列 total_flow flow_df[‘value’].sum() flow_df[‘percentage’] flow_df[‘value’] / total_flow * 100 # 保留占比大于1%的流其他的归为‘其他’ major_flows flow_df[flow_df[‘percentage’] 1].copy() minor_flows_value flow_df[flow_df[‘percentage’] 1][‘value’].sum() if minor_flows_value 0: # 添加一个“其他”汇总节点和连接这里需要根据业务逻辑定义“其他”的来源和目标 # 通常“其他”会作为一个独立的节点连接到主要的源或目标上逻辑较复杂需具体设计。 pass简化逻辑审视你的业务逻辑是否真的需要展示所有层级的细节也许可以合并某些中间节点绘制一个更高层级的概览图。使用静态导出对于非常大的图交互可能变得卡顿。考虑使用fig.write_image()导出为高分辨率静态图片PNG, SVG, PDF在报告中使用。SVG格式是矢量图无限放大不模糊非常适合印刷品。6.2 多级桑基图与故事线串联有时单张桑基图可能太拥挤或者你想展示流程中不同阶段的故事。可以考虑绘制多个关联的桑基图分别展示流程的不同阶段例如“获客阶段”、“转化阶段”、“留存阶段”并通过一致的配色和节点命名将它们串联起来在PPT或报告中并排展示形成一条完整的故事线。另一种高级用法是动态桑基图Animation展示流量随时间的变化。这需要你的数据包含时间维度如年、月。在Plotly中你可以创建多个“帧”frame每个帧是某个时间点的桑基图数据然后通过滑块或动画按钮控制。这涉及到更复杂的go.Frame和animation参数设置是桑基图应用的进阶领域。7. 常见问题排查与实战心得画了这么多桑基图坑没少踩。下面列几个典型问题和解决方法。问题现象可能原因解决方案图表空白不显示1.source/target索引超出labels列表范围。2.value列表中有非正值0或负数。3. 数据中存在孤立节点只有流入或只有流出。1. 检查索引确保从0开始且连续。2. 过滤掉value 0的数据行。3. 检查数据完整性桑基图通常要求节点有进有出除了最左的源和最右的汇。节点或连接线颜色不符合预期1.node.color或link.color传入的列表长度不对。2. 颜色格式错误不是有效的CSS颜色字符串。1.node.color列表长度应等于labels长度link.color长度应等于value长度。2. 使用‘red’,‘#FF0000’,‘rgb(255,0,0)’等标准格式。标签严重重叠无法辨认1. 节点过多自动布局算法无法处理。2. 标签文字过长。1. 尝试手动设置node.x和node.y进行精确定位。2. 缩写标签或使用\n换行。3. 考虑聚合数据减少节点数量。连接线看起来太细或太乱1.value值相差悬殊小流量连接线几乎看不见。2. 连接线交叉过多。1. 过滤掉极小流量的连接如占比0.5%。2. 手动调整node.x和node.y的位置优化布局减少交叉。这更像是一门艺术需要耐心调试。导出图片模糊或尺寸不对1. 导出时分辨率scale设置过低。2. 布局中定义的width和height与导出设置不符。1. 使用fig.write_image(“plot.png”, scale2)提高导出倍率scale2是2倍分辨率。2. 在update_layout中设置好合适的width和height单位是像素导出时会以此为准。最后分享一个我的独家技巧调试时先用小数据集。在开发阶段用一个只有5-10个节点的小样本数据来调试颜色、布局、标签等所有样式参数。等样式完全满意后再替换成全量数据。这样可以避免在大数据上反复渲染等待极大提升效率。绘制桑基图是一个从数据理解到视觉表达的过程。工具的使用只是最后一步更重要的是前期的数据梳理和业务逻辑构建。一张优秀的桑基图应该能让读者在几秒钟内抓住最主要的流量路径和关键节点颜色和布局都是为了这个目标服务的。多练习多思考业务场景你也能创造出既有洞察力又有美感的桑基图。