Matplotlib双Y轴实战:从基础绘图到高级图例合并技巧
1. 为什么需要双Y轴图表在日常数据分析工作中我们经常会遇到需要同时展示两种不同量纲数据的情况。比如销售数据中既要展示绝对销量单位吨又要展示环比增长率百分比或者气象数据中需要同时显示温度摄氏度和降水量毫米。这时候如果只用单Y轴就会出现一个数据系列被压缩得几乎看不见的情况。我去年在做电商数据分析时就遇到过这个问题需要同时展示订单量和转化率订单量在几千级别而转化率都是小于1的小数。直接绘制的结果就是转化率曲线变成了一条贴着X轴的直线完全看不出波动趋势。这时候双Y轴就派上用场了。双Y轴图表的本质是通过共享X轴在图表右侧添加第二个独立的Y轴坐标系。这样两个数据系列都能以合适的比例显示便于观察它们之间的关联性。Matplotlib通过twinx()方法可以轻松实现这个功能这也是Python数据可视化中最常用的技巧之一。2. 基础双Y轴绘制实战2.1 准备示例数据我们先创建一个模拟数据集来演示基础用法。假设我们要分析某产品2023年上半年的销售情况import matplotlib.pyplot as plt import numpy as np # 设置中文显示 plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False # 创建示例数据 months [1月, 2月, 3月, 4月, 5月, 6月] sales np.array([120, 150, 180, 210, 160, 190]) # 单位万元 growth_rate np.array([np.nan, 0.25, 0.2, 0.17, -0.24, 0.19]) # 环比增长率2.2 创建基础双Y轴图表现在我们来绘制包含柱状图销售额和折线图增长率的双Y轴图表fig, ax1 plt.subplots(figsize(10, 6)) # 第一个Y轴左侧- 柱状图 ax1.bar(months, sales, colorskyblue, alpha0.7, label销售额) ax1.set_ylabel(销售额万元, fontsize12) ax1.set_ylim(0, 250) # 设置Y轴范围 # 创建第二个Y轴右侧 ax2 ax1.twinx() ax2.plot(months, growth_rate, colortomato, markero, label环比增长) ax2.set_ylabel(环比增长率, fontsize12) ax2.set_ylim(-0.3, 0.3) # 增长率范围设置在-30%到30% ax2.grid(False) # 关闭右侧Y轴的网格线 # 添加标题 plt.title(2023年上半年销售情况分析, fontsize14, pad20) plt.tight_layout() plt.show()这段代码有几个关键点需要注意先通过subplots()创建第一个Y轴ax1使用twinx()方法创建共享X轴的第二个Y轴ax2分别在不同的Y轴上绘制图表元素通过set_ylim()合理设置两个Y轴的范围避免比例失衡右侧Y轴建议关闭网格线grid(False)避免与左侧网格线重叠造成视觉混乱3. 图例合并的常见问题与解决方案3.1 为什么图例会分开显示如果你按照上面的代码运行会发现一个尴尬的问题图例只显示了最后一个绘制的数据系列增长率折线图销售额的柱状图图例不见了。如果尝试分别在两个轴上调用legend()方法又会出现两个分开的图例框既不美观又占用空间。这是因为Matplotlib默认每个Axes对象管理自己的图例。当我们在双Y轴图表中绘制两组数据时它们属于不同的坐标系所以图例自然就分开了。3.2 三种图例合并方法详解方法一手动组合句柄推荐这是最灵活可靠的方式适合大多数场景# 获取两个轴上的图形对象句柄 bars ax1.bar(months, sales, colorskyblue, alpha0.7, label销售额) line, ax2.plot(months, growth_rate, colortomato, markero, label环比增长) # 合并句柄和标签 handles [bars, line] labels [h.get_label() for h in handles] # 在任意一个轴上添加合并后的图例 ax1.legend(handles, labels, locupper left) # 调整图例位置避免遮挡图表 ax1.legend(handles, labels, locupper left, bbox_to_anchor(0, 0.9))方法二使用figure.legend()这种方法适合需要精确定位图例的场景fig.legend(locupper right, bbox_to_anchor(1, 0.9), bbox_transformax1.transAxes)注意这里的bbox_to_anchor参数(0,0)表示左下角(1,1)表示右上角bbox_transformax1.transAxes表示使用ax1的坐标系方法三Line2D代理艺术家对于复杂图表可以创建虚拟的Line2D对象作为图例项from matplotlib.lines import Line2D # 创建自定义图例项 legend_elements [ Line2D([0], [0], colorskyblue, lw4, label销售额), Line2D([0], [0], colortomato, markero, label环比增长) ] ax1.legend(handleslegend_elements, locupper left)4. 高级样式定制技巧4.1 坐标轴样式匹配为了让双Y轴图表更加专业美观我们需要对两个Y轴进行样式协调# 设置左侧Y轴样式 ax1.spines[left].set_color(skyblue) ax1.tick_params(axisy, colorsskyblue) ax1.yaxis.label.set_color(skyblue) # 设置右侧Y轴样式 ax2.spines[right].set_color(tomato) ax2.tick_params(axisy, colorstomato) ax2.yaxis.label.set_color(tomato) # 隐藏顶部和右侧的轴线非必须 ax1.spines[top].set_visible(False) ax2.spines[top].set_visible(False)4.2 刻度线优化当两个Y轴的数据范围差异很大时刻度线可能会显得不协调。我们可以手动设置刻度位置# 设置左侧Y轴刻度每50万元一个刻度 ax1.set_yticks(np.arange(0, 251, 50)) # 设置右侧Y轴刻度每10%一个刻度 ax2.set_yticks(np.arange(-0.3, 0.31, 0.1)) ax2.set_yticklabels([-30%, -20%, -10%, 0%, 10%, 20%, 30%])4.3 添加数据标签为了提升图表可读性我们可以为每个数据点添加数值标签# 为柱状图添加数据标签 for bar in bars: height bar.get_height() ax1.text(bar.get_x() bar.get_width()/2., height, f{height:.0f}, hacenter, vabottom) # 为折线图添加数据标签 for x, y in zip(months, growth_rate): if not np.isnan(y): ax2.text(x, y, f{y:.0%}, hacenter, vabottom)5. 实际项目中的经验分享在最近的一个金融数据分析项目中我需要同时展示股票价格和交易量。价格通常在几百元级别而交易量则是几十万手的级别。直接绘制会导致价格曲线几乎变成一条直线。通过双Y轴技巧我成功实现了两个指标的对比分析。几个实用的调试技巧当图例显示不正常时先检查label参数是否设置正确如果图例位置跑偏尝试调整bbox_to_anchor的值使用fig.tight_layout()可以自动调整图表元素间距保存图片时如果图例被截断可以尝试增加bbox_inchestight参数plt.savefig(dual_axis.png, dpi300, bbox_inchestight)双Y轴图表虽然强大但也要注意不要滥用。当两个数据系列没有直接关联时强行使用双Y轴可能会造成误导。在展示时务必注明两个Y轴分别代表的含义避免解读错误。