1. 堆叠图:从“是什么”到“为什么用它”
如果你做过数据分析或者看过一些行业报告,对“堆叠图”这个名字一定不陌生。它几乎是展示构成与趋势的“标配”图表。但你真的了解它吗?或者说,当你的老板、同事或者客户扔给你一堆数据,要求你“做个图看看各部分占比和变化”时,你脑海里第一个蹦出来的就是堆叠图,这背后有没有什么坑?今天,我们不聊那些“在Excel里点三下就能生成”的入门操作,而是从一个数据可视化实践者的角度,深挖一下堆叠图。我会结合我这些年做报表、写分析报告、甚至是在产品里设计数据看板的实际经验,跟你聊聊堆叠图到底该怎么用、什么时候用,以及那些教程里不会告诉你的“暗礁”。
简单来说,堆叠图(Stacked Chart)是一种将多个数据序列“堆叠”起来展示的图表。它通常有两个核心作用:第一,展示整体(所有序列的总和)随时间或其他维度的变化趋势;第二,展示每个组成部分(单个序列)在整体中的占比及其变化。听起来很完美,对吧?既能看总量,又能看结构。但正是这种“全能”的印象,让它成了最容易被误用和滥用的图表类型之一。很多人只看到了它的“能”,却没想清楚它“不能”什么,以及“在什么条件下才能”。
举个例子,你想分析公司过去一年各产品线的营收情况。用堆叠柱状图,X轴是月份,Y轴是营收额,每个柱子被不同颜色的区块分割,代表A、B、C三条产品线。一眼看去,你既能知道每月总营收是涨是跌,也能看出每条产品线贡献了多少。这似乎是标准答案。但问题来了:如果B产品线在6月突然爆发,营收翻倍,而A、C产品线基本持平,这个柱子的高度会显著增加。这时,你想比较1月和6月A产品线的绝对营收值,你的视线需要努力地去对比两个不同高度柱子底部的那一小段“A色块”的起点和终点,这非常反直觉且容易出错。这就是堆叠图最大的局限:它擅长展示“部分”与“整体”的关系,但极不擅长比较不同分类下同一个“部分”的绝对值。
所以,在决定使用堆叠图之前,你必须先问自己两个问题:第一,我传达信息的重点,是“整体趋势”和“各部分的相对占比”,还是“不同类别下同一部分的数值比较”?第二,我的数据序列之间,是“组成部分”的关系(加起来等于一个有意义的总和),还是彼此独立、只是放在一起对比?如果你的答案是前者,那么堆叠图可能是一个好选择;如果是后者,那么分组柱状图(Grouped Bar Chart)或者折线图可能是更清晰的选择。理解这个根本性的“适用域”,是玩转堆叠图的第一步,也是避免做出误导性图表的关键。
2. 堆叠图的家族:不止是柱状图
提到堆叠图,大多数人第一反应是“堆叠柱状图”。这没错,但它的家族成员比你想象的要丰富。不同类型的堆叠图适用于不同的数据特性和分析场景,选错了类型,效果会大打折扣。
2.1 堆叠柱状图:经典的比例与趋势展示器
这是最常见的一种。X轴通常是分类数据(如时间、地区、产品类别),Y轴是数值。每个柱子代表一个分类下的整体,柱子内部按颜色分割为不同的组成部分。
核心适用场景:
- 比较不同分类的整体数值:例如,比较北京、上海、广州、深圳四个城市的年度总销售额(柱子高度)。
- 分析每个分类的内部构成:例如,分析每个城市销售额中,线上、线下渠道各自的占比(柱子内部色块比例)。
- 观察构成比例随分类的变化趋势:例如,观察从北京到深圳,线上销售占比是否逐渐升高(同一色块在不同柱子中的宽度变化)。
实操心得:
- 排序很重要:通常建议将最大的组成部分放在底部(紧贴X轴),最小的放在顶部。这样图形最稳定,也便于阅读。你可以通过预处理数据或绘图库的排序功能实现。
- 颜色选择:使用色相区分度明显的颜色来代表不同类别。如果组成部分有自然顺序(如“差、中、良、优”),可以考虑使用同一色系的不同深浅(顺序色系)。避免使用过多颜色,一般不超过6-7种,否则会显得杂乱。
- 标签策略:直接在狭窄的色块上标注数值和百分比非常困难且不美观。通常的做法是:1)在图例中清晰说明颜色对应的部分;2)在柱子顶端标注整体数值;3)对于需要突出显示的关键部分(如占比最大的部分),可以尝试在色块内部或旁边进行标注。交互式图表中,常用悬停(Tooltip)来显示详细信息,这是最佳实践。
2.2 堆叠面积图:感受“流”与“势”
如果把堆叠柱状图的柱子“连接”起来,并用颜色填充柱子之间的区域,你就得到了堆叠面积图。它的X轴通常是连续变量,最常见的是时间。
核心适用场景:
- 强调趋势和累积效应:特别适合展示随时间推移,多个组成部分的累积量如何变化,以及整体的趋势。例如,展示一款应用每日新增用户中,来自不同渠道(应用商店、搜索引擎、社交媒体)的用户累积情况。
- 感受“流量”与“组成”:面积图给人一种“流动”和“容量”的直观感受。你能清晰地看到每个组成部分的“厚度”如何随时间变化,以及它们如何共同塑造了整体的轮廓。
与堆叠柱状图的本质区别: 堆叠柱状图强调在离散点上的“构成快照”,而堆叠面积图强调在连续区间上的“演变过程”。对于时间序列数据,面积图通常能提供更流畅的视觉体验。
实操心得与巨坑预警:
- “透视扭曲”问题:这是堆叠面积图最著名的陷阱。由于所有部分都是从底部开始堆叠,只有最底部的序列(紧贴X轴的那一块)的轮廓是它真实的数值变化曲线。上面的所有序列,其形状都受到下方所有序列数值波动的影响。你不能通过观察某个颜色区域的上下边界线来直接判断该部分数值的变化趋势!例如,即使某个部分的实际数值在整个时间段内是恒定的,如果它下方的部分数值剧烈波动,它在面积图中的形状也会随之起伏,造成“该部分数值也在波动”的错觉。
- 如何规避?如果你需要清晰比较各个部分自身的趋势,应该使用折线图,并将多条折线画在一起。堆叠面积图的核心价值在于展示“整体趋势”和“各部分的累积贡献”,而不是比较各部分自身的波动。在使用前,务必向你的观众说明这一点,或者在图表旁添加注释。
- 透明度使用:有时为了美观或看清被遮挡的部分,会给上层面积设置一定的透明度。但这要谨慎,因为混合的颜色可能产生新的色相,造成误解。通常不建议透明度超过20%。
2.3 百分比堆叠图:纯比例的世界
无论是柱状图还是面积图,都可以将其Y轴刻度从“绝对值”转换为“百分比”。在这种图表中,每个柱子或每个时间点的总高度都是100%,内部色块代表该组成部分在整体中的占比。
核心适用场景:当你想纯粹地比较构成比例,而完全不关心绝对总量时。例如,分析公司市场份额的历年变化(你只关心比例,不关心市场总规模),或者分析用户时间分配在不同APP上的比例变化。
注意事项: 百分比堆叠图彻底放弃了绝对值信息。这意味着,一个占比从50%下降到40%的部分,其绝对数值可能是在增长的(如果整体在快速增长)。所以,永远不要单独使用百分比堆叠图来下结论,最好搭配一个显示整体绝对值的简单图表(如折线图或普通柱状图)一起呈现。
2.4 其他变体:堆叠条形图、瀑布图
- 堆叠条形图:本质上是将堆叠柱状图旋转90度。当分类名称较长,横向排列更节省空间、更易阅读时使用。所有适用于堆叠柱状图的准则同样适用。
- 瀑布图:可以看作是一种特殊的、动态的堆叠图,它展示一个初始值如何经过一系列正值(增加)和负值(减少)的“堆叠”,最终达到一个终止值。常用于财务分析,如展示从营收到净利润的演变过程。它强调中间过程的“贡献”而非静态的“构成”。
3. 实战:用Python(Matplotlib/Seaborn)绘制专业的堆叠图
理论说再多,不如动手画一张。这里我们以最常用的Python可视化库Matplotlib(及其高级接口Seaborn)为例,手把手走一遍流程,并重点讲解那些容易出错的关键参数和配置。
假设我们有一组数据,记录了某公司2023年四个季度(Q1-Q4)在三个产品线(Product A, B, C)上的销售额(单位:万元)。
import matplotlib.pyplot as plt import numpy as np import pandas as pd # 准备数据 quarters = ['Q1', 'Q2', 'Q3', 'Q4'] product_a = [120, 135, 158, 140] product_b = [90, 110, 105, 130] product_c = [60, 75, 88, 95] data = pd.DataFrame({ 'Quarter': quarters, 'Product A': product_a, 'Product B': product_b, 'Product C': product_c })3.1 基础堆叠柱状图绘制
# 设置图形大小和分辨率 plt.figure(figsize=(10, 6), dpi=100) # 确定底部起点(bottom)数组 # 对于第一个序列(Product A),底部起点为0 bottom_a = np.zeros(len(quarters)) # 对于第二个序列(Product B),底部起点是Product A的值 bottom_b = product_a # 对于第三个序列(Product C),底部起点是Product A + Product B的值 bottom_c = np.array(product_a) + np.array(product_b) # 分别绘制三个柱状图,并指定底部位置 bars_a = plt.bar(quarters, product_a, label='Product A', color='#2E86AB', edgecolor='white') bars_b = plt.bar(quarters, product_b, bottom=bottom_b, label='Product B', color='#A23B72', edgecolor='white') bars_c = plt.bar(quarters, product_c, bottom=bottom_c, label='Product C', color='#F18F01', edgecolor='white') # 添加标签和标题 plt.xlabel('Quarter', fontsize=12) plt.ylabel('Sales (10k Yuan)', fontsize=12) plt.title('Quarterly Sales by Product Line (Stacked)', fontsize=14, fontweight='bold') plt.legend(title='Product Line', title_fontsize=11, fontsize=10) # 在柱子顶部添加总计标签 total_sales = np.array(product_a) + np.array(product_b) + np.array(product_c) for i, total in enumerate(total_sales): plt.text(i, total + 5, f'{total:.0f}', ha='center', va='bottom', fontsize=10) # 优化网格和布局 plt.grid(axis='y', alpha=0.3, linestyle='--') plt.ylim(0, max(total_sales) * 1.15) # 留出顶部标签空间 plt.tight_layout() plt.show()关键点解析:
bottom参数:这是实现堆叠的核心。每个序列的bottom参数指定了其绘制的起点Y坐标。第二个序列的起点是第一个序列的值,第三个序列的起点是前两个序列值之和。手动计算bottom数组是理解堆叠原理的好方法,但对于更多序列,用循环或np.cumsum(累积和)函数更高效。edgecolor:设置柱子边缘为白色,能在色块之间形成清晰的分隔线,增强可读性,尤其在打印或背景色复杂时。- 总计标签:在柱子顶部标注总和,让读者一眼看到整体规模,这是一个非常实用的技巧。注意通过
ylim调整Y轴上限,为标签留出空间。 - 颜色:我选择了三个在色环上间隔较远、且饱和度明度搭配和谐的颜色(来自ColorBrewer等配色方案的灵感),确保区分度。
3.2 使用DataFrame和内置方法简化
对于结构化数据(如Pandas DataFrame),有更简洁的绘制方法:
# 设置图形 plt.figure(figsize=(10, 6)) # 提取需要堆叠的数据列 stack_data = data.set_index('Quarter')[['Product A', 'Product B', 'Product C']] # 使用DataFrame的plot方法直接绘制堆叠图 ax = stack_data.plot(kind='bar', stacked=True, color=['#2E86AB', '#A23B72', '#F18F01'], edgecolor='white', figsize=(10,6)) # 后续装饰步骤类似 ax.set_xlabel('Quarter', fontsize=12) ax.set_ylabel('Sales (10k Yuan)', fontsize=12) ax.set_title('Quarterly Sales by Product Line (Stacked) - DataFrame Method', fontsize=14, fontweight='bold') ax.legend(title='Product Line', title_fontsize=11) # 添加总计标签 for container in ax.containers: # container包含了同一产品线在所有季度的柱子集合 # 但我们更需要每个柱子的顶部总和,所以用另一种方法 pass # 更直接地计算并标注总计 for i, quarter in enumerate(quarters): total = stack_data.loc[quarter].sum() ax.text(i, total + 5, f'{total:.0f}', ha='center', va='bottom', fontsize=10) ax.grid(axis='y', alpha=0.3, linestyle='--') ax.set_ylim(0, stack_data.sum(axis=1).max() * 1.15) plt.tight_layout() plt.show()df.plot(kind='bar', stacked=True)是最常用的快捷方式。但要注意,它默认的图例顺序和柱子堆叠顺序可能与数据列的排列顺序一致,通常是你想要的效果。
3.3 绘制堆叠面积图
plt.figure(figsize=(12, 7)) # 准备数据:需要将时间/分类转换为连续的X坐标(这里用索引) x = range(len(quarters)) # 计算累积和,作为每个序列绘制的下边界(对于面积图,是从下往上填充) y_a = np.array(product_a) y_b = y_a + np.array(product_b) y_c = y_b + np.array(product_c) # 绘制面积图,使用 fill_between plt.fill_between(x, 0, y_a, label='Product A', color='#2E86AB', alpha=0.8, edgecolor='black', linewidth=0.5) plt.fill_between(x, y_a, y_b, label='Product B', color='#A23B72', alpha=0.8, edgecolor='black', linewidth=0.5) plt.fill_between(x, y_b, y_c, label='Product C', color='#F18F01', alpha=0.8, edgecolor='black', linewidth=0.5) # 也可以绘制顶部轮廓线,让趋势更明显 plt.plot(x, y_a, color='#2E86AB', linewidth=1.5) plt.plot(x, y_b, color='#A23B72', linewidth=1.5) plt.plot(x, y_c, color='#F18F01', linewidth=1.5) # 装饰图表 plt.xticks(x, quarters) # 将X轴刻度标签设置为季度 plt.xlabel('Quarter', fontsize=12) plt.ylabel('Cumulative Sales (10k Yuan)', fontsize=12) # Y轴标签改为“累积销售额” plt.title('Quarterly Sales Trend by Product Line (Stacked Area)', fontsize=14, fontweight='bold') plt.legend(title='Product Line', loc='upper left') plt.grid(alpha=0.3) plt.tight_layout() plt.show()关键点解析:
fill_between:这是绘制面积图的核心函数。参数(x, y1, y2)表示在x范围内,填充y1和y2两条线之间的区域。通过依次填充从0到y_a, y_a到y_b, y_b到y_c的区域,我们实现了堆叠。alpha(透明度):这里设置为0.8,既保证了颜色的饱满度,又让底部区域若隐若现,层次感更好。但如前所述,需谨慎使用高透明度。- 绘制顶部轮廓线:在填充区域之上,用
plot函数绘制每条堆叠区域的顶线,可以更清晰地勾勒出每个部分的上边界,有助于阅读。线的颜色通常与填充色一致或更深。
3.4 使用Seaborn提升效率与美观度
Seaborn基于Matplotlib,提供了更高级的API和更好的默认样式。虽然Seaborn没有直接的“堆叠条形图”函数,但我们可以利用其histplot(用于数值分布)或结合Pandas的plot方法,并享受Seaborn的样式管理。
import seaborn as sns # 设置Seaborn主题 sns.set_theme(style="whitegrid") # 将数据转换为“长格式”(Tidy Data),这是Seaborn偏好的格式 data_long = data.melt(id_vars='Quarter', var_name='Product', value_name='Sales') print(data_long) # 使用Catplot或Barplot需要手动计算堆叠,不如直接使用Pandas plot方便。 # 更常见的做法是:用Seaborn设置样式,用Pandas/Matplotlib绘图。 plt.figure(figsize=(10,6)) # 继续使用之前的stack_data DataFrame ax = stack_data.plot(kind='bar', stacked=True, color=sns.color_palette("Set2", 3), edgecolor='w', linewidth=1.5, figsize=(10,6)) # 应用Seaborn的样式优化(网格已由whitegrid主题提供) sns.despine(left=True, bottom=True) # 移除上方和右侧的边框线,让图表更简洁 ax.set_xlabel('Quarter', fontsize=12) ax.set_ylabel('Sales (10k Yuan)', fontsize=12) ax.set_title('Quarterly Sales by Product Line (Seaborn Styled)', fontsize=14, fontweight='bold') ax.legend(title='Product Line') # 添加标签 for i, total in enumerate(total_sales): ax.text(i, total + 3, f'{total:.0f}', ha='center', va='bottom', fontsize=9, color='dimgrey') plt.tight_layout() plt.show()核心技巧:sns.set_theme()一行代码就能获得协调的配色、字体和网格样式。sns.despine()可以移除图表四周的边框线(脊柱),让视觉焦点更集中在数据上,是现代图表设计的常用技巧。sns.color_palette()提供了大量优秀的配色方案,如"Set2","husl","tab10"等,直接调用即可,省去自己配色的烦恼。
4. 高级技巧与避坑指南:让堆叠图真正“说话”
画出一个堆叠图很容易,但画出一个准确、清晰、高效传达信息的堆叠图,需要细节上的打磨和对潜在问题的预判。
4.1 数据排序与图例顺序
问题:默认情况下,绘图库会按照数据提供的顺序进行堆叠和生成图例。如果数据是随意排列的,可能会导致柱子内部色块顺序混乱,图例与图形对应关系不直观。
解决方案:
- 按值排序:在绘图前,对数据进行排序。通常有两种策略:
- 整体排序:计算每个组成部分在所有分类上的总和,按总和从大到小排序。这样能在大多数柱子里,将最大的部分放在底部,图形最稳定。使用Pandas可以轻松实现:
stack_data = stack_data[stack_data.sum().sort_values(ascending=False).index]。 - 首次出现排序:按第一个分类(如第一季度)的数值从大到小排序。这能保证第一个柱子的顺序最优,但后续柱子可能不一致。适用于时间序列,且你认为第一个时间点具有代表性。
- 整体排序:计算每个组成部分在所有分类上的总和,按总和从大到小排序。这样能在大多数柱子里,将最大的部分放在底部,图形最稳定。使用Pandas可以轻松实现:
- 图例同步:确保图例的顺序与堆叠顺序一致。在使用
df.plot()时,这通常是自动的。如果手动绘制,创建图例时传入的label列表顺序应与绘制顺序一致。
4.2 处理负值与零值
问题:当数据中包含负值时,堆叠逻辑会变得复杂。Matplotlib的bottom参数允许负值,但视觉效果可能令人困惑——负值部分会向下堆叠。零值虽然可以绘制,但可能因为四舍五入或显示精度问题,在图上呈现为一个极细的线或点,影响美观。
解决方案与建议:
- 审慎使用负值:如果数据中普遍存在负值(如利润数据,既有盈利又有亏损),堆叠图可能不是最佳选择。考虑使用分组柱状图来并排显示正负值,或者使用瀑布图来展示从正到负的演变过程。
- 如果必须用:确保颜色区分明显,并考虑添加一条Y=0的基准线。对于接近零的微小正值或负值,可以设定一个显示阈值,例如绝对值小于某个数(如总和的0.5%)时,在图上不绘制该色块,或在Tooltip中说明。
- 零值处理:对于确为零的值,绘图库会正常处理。但如果你遇到因为数值精度导致的“近似零”在图上产生奇怪效果,可以在绘图前对数据进行清洗,将绝对值极小的数(如
1e-10)直接设为0。
4.3 标签与注释的艺术
在静态图表中,在堆叠的色块内部添加文本标签非常挑战,尤其是当色块很窄的时候。以下是几种策略:
- 直接标注(谨慎使用):只对足够宽(例如占比超过15%)的色块,在内部中心位置添加百分比或数值。使用与色块对比度高的文字颜色(如在深色块上用白色字,浅色块上用黑色字)。可以通过计算色块的亮度来自动决定文字颜色。
- 外部引线标注:对于重要的、但较窄的组成部分,可以使用引线将标签引到柱子外部。这适用于强调某个特定部分,但会使得图表变得复杂。
- 悬停交互(最佳实践):在网页或交互式报告(使用Plotly、Bokeh、ECharts等库)中,Tooltip(悬停提示)是解决堆叠图标签问题的终极方案。用户将鼠标悬停在某个色块上时,动态显示该部分的详细信息(名称、数值、占比)。这保持了图表的整洁,同时提供了完整的信息。
- 聚焦关键信息:很多时候,你不需要标注每一个部分。只在标题、副标题或图表旁的注释中说明整体结论,而将细节数据留给有兴趣的读者通过图例和坐标轴去估算,或通过交互方式获取。
4.4 颜色选择的深层逻辑
颜色不是为了让图表“好看”,而是为了“好懂”。
- 分类数据:使用定性色板(Qualitative Palette),即色相差异明显的颜色。Seaborn的
Set2,Set3,tab20c, Matplotlib的tab20都是很好的选择。避免使用彩虹色(spectral),因为人眼对其中某些颜色的亮度感知不同,容易误导。 - 有序数据:如果组成部分有内在顺序(如“低、中、高”),使用顺序色板(Sequential Palette),即同一色系从浅到深。例如
Blues,Greens,Oranges。这能直观传达“量”的大小。 - 发散数据:如果数据围绕一个中点(如零值、平均值)向两端发散(正/负),使用发散色板(Diverging Palette),如
RdBu_r(红-蓝),中间用浅色表示接近中点的值。 - 可访问性:考虑色盲友好配色。避免同时使用红色和绿色作为主要区分色。可以使用在线工具(如ColorBrewer)或Seaborn内置的
colorblind友好色板。 - 一致性:在同一份报告或仪表板中,代表相同含义的分类(如“产品A”)应始终保持相同的颜色。
4.5 交互式堆叠图的实现思路
对于现代数据分析和汇报场景,静态图表越来越难以满足深度探索的需求。交互式堆叠图能极大提升体验。
库的选择:
- Plotly / Plotly Express:上手极快,交互功能强大(缩放、平移、悬停、点击高亮、图例开关),且图表美观。只需将
barmode='stack'或facet等参数即可创建堆叠图。 - Bokeh:提供更底层的控制,适合构建复杂的交互式数据应用。需要编写更多代码来定义交互行为。
- Pyecharts / ECharts:基于百度ECharts,图表类型丰富,交互流畅,中文文档友好。
- Altair / Vega-Lite:声明式语法,基于JSON规范,生成的是Vega-Lite图表,可以在多个前端环境中渲染,非常优雅。
- Plotly / Plotly Express:上手极快,交互功能强大(缩放、平移、悬停、点击高亮、图例开关),且图表美观。只需将
核心交互功能:
- 悬停提示(Tooltip):如前所述,这是必备功能,显示精确数值和百分比。
- 图例开关(Legend Toggle):点击图例可以显示/隐藏对应的数据序列,方便用户聚焦感兴趣的部分。
- 缩放与平移(Zoom/Pan):对于数据点密集的堆叠面积图尤其有用。
- 数据高亮(Highlight):鼠标悬停时,高亮对应的数据序列或数据点。
- 下钻(Drill-down):例如,点击“华东区”的柱子,可以下钻显示华东区各省份的堆叠图。
实现交互式图表通常意味着你需要将可视化嵌入到网页中。对于Python后端,可以结合Flask/Django等框架,将Plotly或Bokeh生成的HTML片段嵌入模板;对于数据分析报告,可以使用Jupyter Notebook(Plotly、Bokeh、ipywidgets有很好的支持)或生成独立的HTML文件。
5. 堆叠图的“近亲”与替代方案:何时不用堆叠图?
认识到堆叠图的局限性,并知道何时该选择其他图表,是数据素养的重要体现。
5.1 分组柱状图:当比较“部分”的绝对值更重要时
场景:回到开头的例子,如果你想精确比较每个季度“产品A”自身的销售额变化,或者比较同一季度内三个产品的销售额高低。
选择:使用分组柱状图(Grouped Bar Chart)。它将同一分类下的不同序列并排排列,共享同一个基线(X轴),因此每个柱子的高度直接代表其绝对值,比较起来毫无障碍。
Matplotlib实现:
plt.figure(figsize=(10,6)) x = np.arange(len(quarters)) width = 0.25 # 柱子的宽度 plt.bar(x - width, product_a, width, label='Product A', color='#2E86AB') plt.bar(x, product_b, width, label='Product B', color='#A23B72') plt.bar(x + width, product_c, width, label='Product C', color='#F18F01') plt.xlabel('Quarter') plt.ylabel('Sales (10k Yuan)') plt.title('Quarterly Sales by Product Line (Grouped Bar)') plt.xticks(x, quarters) plt.legend() plt.tight_layout() plt.show()5.2 折线图:当关注每个“部分”自身的趋势时
场景:如果你想观察“产品A”、“产品B”、“产品C”各自销售额随时间的变化趋势,并且趋势的细节(如拐点、增长率)很重要。
选择:使用多系列折线图(Multiple Line Chart)。多条折线在同一个坐标系中,可以清晰对比每条线自身的走势和不同线之间的相对位置。
注意:当折线数量较多(如超过5条)时,可能会产生视觉混乱。这时可以考虑使用小多图(Small Multiples),即为每个序列单独绘制一个折线图,共享X轴和Y轴,方便逐个仔细查看和跨图比较。
5.3 瀑布图:展示演变的“中间过程”
场景:展示一个初始值如何通过一系列增加和减少,达到最终值。例如:期初库存 + 采购 - 销售 = 期末库存。
选择:瀑布图(Waterfall Chart)。它用悬浮的柱子表示每个正负贡献,连接线显示了累积过程。
实现:Matplotlib没有原生瀑布图函数,但可以通过巧妙设置柱子的bottom参数和添加连接线来实现。也可以使用专门的库如waterfallcharts(较老)或plotly(go.Waterfall)。
5.4 桑基图:展示“流量”与“转化”
场景:展示数据在不同状态或类别之间的流动。例如:用户从不同渠道进入网站,然后转化为注册、付费等不同行为。
选择:桑基图(Sankey Diagram)。它用箭头的粗细表示流量大小,清晰展示了来源、去向和转化路径。堆叠图只能展示静态的构成,而桑基图展示了动态的流转。Plotly和Pyecharts对桑基图有很好的支持。
决策流程图: 当你拿到一份数据,想展示“部分与整体”的关系时,可以快速问自己:
- 我的数据是静态构成,还是动态流转?流转 -> 考虑桑基图。
- 我主要想比较各部分的绝对值,还是占比?绝对值 -> 分组柱状图或多折线图。
- 我主要想展示整体趋势,还是各部分自身趋势?各部分自身趋势 -> 多折线图。
- 我的X轴是分类还是连续时间?连续时间 -> 堆叠面积图或折线图。
- 我的数据中是否有负值?且需要展示累积过程?是 -> 瀑布图或谨慎使用堆叠图(带基准线)。
- 以上都不是,我就是想清晰地展示不同分类下,各部分的构成及其对总体的贡献-> 堆叠柱状图(或百分比堆叠图)。
说到底,堆叠图是一个强大的工具,但绝非万能。它的核心优势在于直观展示“部分”如何组成“整体”,以及这个“整体”如何变化。理解它的能力边界,结合具体的数据特性和分析目标,才能让它成为你手中真正有力的沟通武器,而不是又一个制造视觉混乱的图表垃圾。在我自己的工作中,我养成了一个习惯:每当用堆叠图做完初稿,我都会问自己——“如果我把这个图换成折线图或分组柱状图,核心信息会不会表达得更清晰?” 这个简单的自省,帮我避免了很多次潜在的可视化失误。