双Y轴图表是数据可视化中常见的图表类型,它允许我们在同一图表中展示两个不同量纲的数据系列。本文将全面介绍使用Matplotlib创建双Y轴图表的方法,包括基础实现、样式定制以及常见应用场景。
一、双Y轴图表基础
1. 双Y轴图表适用场景
双Y轴图表特别适合以下情况:
- 比较两个不同单位但有关联的数据(如温度和降雨量)
- 展示同一指标的不同量级变化(如销售额和增长率)
- 对比不同量纲的趋势关系(如价格和交易量)
2. 基本实现方法
Matplotlib通过twinx()
和twiny()
方法实现双轴,最常用的是twinx()
(共享X轴的双Y轴):
import matplotlib.pyplot as plt
import numpy as np
# 创建图形和第一个轴
fig, ax1 = plt.subplots(figsize=(10, 6))
# 生成示例数据
x = np.arange(0, 10, 0.1)
y1 = np.sin(x)
y2 = np.exp(x) * 1000
# 绘制第一个Y轴数据
ax1.plot(x, y1, 'b-', label='sin(x)')
ax1.set_xlabel('X轴')
ax1.set_ylabel('sin(x)', color='b')
ax1.tick_params(axis='y', labelcolor='b')
# 创建第二个Y轴
ax2 = ax1.twinx()
ax2.plot(x, y2, 'r-', label='exp(x)')
ax2.set_ylabel('exp(x)', color='r')
ax2.tick_params(axis='y', labelcolor='r')
# 添加标题和图例
plt.title('双Y轴示例图表')
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax2.legend(lines1 + lines2, labels1 + labels2, loc='upper left')
plt.show()
二、样式定制技巧
1. 轴标签对齐
# 调整第二个Y轴标签位置避免重叠
ax2.yaxis.set_label_coords(1.1, 0.5) # (x,y)坐标
2. 网格线控制
# 只显示主Y轴的网格线
ax1.grid(True)
ax2.grid(False)
3. 颜色与线型搭配
# 使用不同线型和颜色增强区分度
ax1.plot(x, y1, 'b--', linewidth=2, label='线性增长')
ax2.plot(x, y2, 'r:', linewidth=2, label='指数增长')
4. 轴范围同步
# 同步X轴范围
ax1.set_xlim(0, 10)
ax2.set_xlim(0, 10)
# 设置不同的Y轴范围
ax1.set_ylim(-1.5, 1.5)
ax2.set_ylim(0, 22000)
三、高级应用场景
1. 柱状图+折线图组合
# 数据准备
months = ['Jan', 'Feb', 'Mar', 'Apr', 'May']
sales = [120, 145, 160, 185, 210]
growth_rate = [0.15, 0.12, 0.18, 0.22, 0.20]
fig, ax1 = plt.subplots(figsize=(10, 6))
# 柱状图(主Y轴)
bars = ax1.bar(months, sales, color='skyblue', alpha=0.7, label='销售额(万元)')
ax1.set_ylabel('销售额(万元)')
# 折线图(次Y轴)
ax2 = ax1.twinx()
line = ax2.plot(months, growth_rate, 'r-o', linewidth=2, label='增长率')
ax2.set_ylabel('增长率', color='r')
ax2.tick_params(axis='y', labelcolor='r')
ax2.set_ylim(0, 0.3) # 固定增长率范围
# 添加数据标签
for bar in bars:
height = bar.get_height()
ax1.text(bar.get_x() + bar.get_width()/2., height,
f'{height:.0f}',
ha='center', va='bottom')
for x, y in zip(months, growth_rate):
ax2.text(x, y, f'{y:.0%}',
ha='center', va='bottom', color='r')
# 合并图例
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper left')
plt.title('销售额与增长率对比')
plt.show()
2. 三个Y轴实现
from mpl_toolkits.axes_grid1 import host_subplot
import mpl_toolkits.axisartist as AA
# 创建主轴
host = host_subplot(111, axes_class=AA.Axes)
plt.subplots_adjust(right=0.75)
# 创建两个附加轴
par1 = host.twinx()
par2 = host.twinx()
# 调整第二个附加轴位置
offset = 60
new_fixed_axis = par2.get_grid_helper().new_fixed_axis
par2.axis["right"] = new_fixed_axis(loc="right",
axes=par2,
offset=(offset, 0))
par2.axis["right"].toggle(all=True)
# 绘制数据
host.set_xlim(0, 10)
host.set_ylim(0, 10)
host.set_xlabel("X轴")
host.set_ylabel("Y轴1")
par1.set_ylabel("Y轴2")
par2.set_ylabel("Y轴3")
p1, = host.plot([0, 1, 2], [0, 1, 2], label="Y1")
p2, = par1.plot([0, 1, 2], [0, 3, 2], label="Y2")
p3, = par2.plot([0, 1, 2], [0, 2, 4], label="Y3")
host.legend()
host.axis["left"].label.set_color(p1.get_color())
par1.axis["right"].label.set_color(p2.get_color())
par2.axis["right"].label.set_color(p3.get_color())
plt.show()
3. 共享双Y轴子图
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
# 第一个子图
ax1.plot(x, y1, 'b-')
ax1.set_ylabel('Y1', color='b')
ax1_2 = ax1.twinx()
ax1_2.plot(x, y2, 'r-')
ax1_2.set_ylabel('Y2', color='r')
# 第二个子图
ax2.plot(x, np.cos(x), 'g-')
ax2.set_ylabel('Y3', color='g')
ax2_2 = ax2.twinx()
ax2_2.plot(x, np.log(x+1), 'm-')
ax2_2.set_ylabel('Y4', color='m')
plt.tight_layout()
plt.show()
四、常见问题解决方案
1. 图例合并问题
# 更优雅的图例合并方法
from matplotlib.lines import Line2D
# 创建自定义图例元素
legend_elements = [
Line2D([0], [0], color='b', lw=2, label='系列1'),
Line2D([0], [0], color='r', lw=2, label='系列2'),
Line2D([0], [0], marker='o', color='w', label='数据点',
markerfacecolor='g', markersize=10)
]
ax1.legend(handles=legend_elements, loc='upper right')
2. 轴刻度对齐
# 使两个Y轴的0刻度对齐
def align_yaxis(ax1, ax2):
y1_min, y1_max = ax1.get_ylim()
y2_min, y2_max = ax2.get_ylim()
ax1.set_ylim(y1_min, y1_max)
ax2.set_ylim(y1_min * (y2_max/y1_max), y1_max * (y2_max/y1_max))
align_yaxis(ax1, ax2)
3. 轴标签重叠
# 调整边距和标签位置
plt.subplots_adjust(right=0.85) # 为第二个Y轴留出空间
ax2.yaxis.set_label_coords(1.15, 0.5) # 调整第二个Y轴标签位置
五、样式美化进阶
1. 使用Seaborn风格
import seaborn as sns
sns.set_style("whitegrid")
fig, ax1 = plt.subplots(figsize=(10, 6))
# 绘图代码...
2. 自定义轴线样式
# 设置轴线样式
ax1.spines['left'].set_color('blue')
ax1.spines['left'].set_linewidth(2)
ax2.spines['right'].set_color('red')
ax2.spines['right'].set_linewidth(2)
# 隐藏顶部和右侧轴线
ax1.spines['top'].set_visible(False)
ax2.spines['top'].set_visible(False)
3. 添加填充效果
# 在双Y轴图表中添加填充
ax1.fill_between(x, 0, y1, color='blue', alpha=0.1)
ax2.fill_between(x, 0, y2, color='red', alpha=0.1)
六、交互式双Y轴图表
1. 使用Plotly创建交互式双Y轴图表
import plotly.graph_objects as go
from plotly.subplots import make_subplots
# 创建带双Y轴的子图
fig = make_subplots(specs=[[{"secondary_y": True}]])
# 添加轨迹
fig.add_trace(
go.Scatter(x=x, y=y1, name="sin(x)"),
secondary_y=False,
)
fig.add_trace(
go.Scatter(x=x, y=y2, name="exp(x)"),
secondary_y=True,
)
# 设置轴标签
fig.update_yaxes(title_text="<b>sin(x)</b>", secondary_y=False)
fig.update_yaxes(title_text="<b>exp(x)</b>", secondary_y=True)
fig.show()
2. 使用mpld3实现Matplotlib交互
import mpld3
fig, ax1 = plt.subplots(figsize=(10, 6))
ax1.plot(x, y1, 'b-', label='sin(x)')
ax1.set_ylabel('sin(x)')
ax2 = ax1.twinx()
ax2.plot(x, y2, 'r-', label='exp(x)')
ax2.set_ylabel('exp(x)')
mpld3.display(fig) # 在浏览器中显示交互式图表
七、最佳实践与注意事项
- 避免误导:确保双Y轴的使用不会造成数据关系的误读
- 清晰标注:明确标注每个Y轴代表的含义和单位
- 颜色区分:使用对比色明确区分不同轴的数据系列
- 比例协调:当比较趋势时,考虑调整比例使两条曲线有可比性
- 慎用三轴:除非绝对必要,否则避免使用三个Y轴,这会大大增加图表复杂度
通过本指南,你应该已经掌握了使用Matplotlib创建各种双Y轴图表的全面技能。从基础实现到高级定制,从静态图表到交互式可视化,这些技术将帮助你更有效地展示多维数据关系。记住,好的数据可视化不仅要技术正确,更要传达清晰准确的信息。