目录
-
-
- [Matplotlib 可视化大师系列博客总览](#Matplotlib 可视化大师系列博客总览)
-
- [Matplotlib 可视化大师系列(八):综合篇 - 在一张图中组合多种图表类型](#Matplotlib 可视化大师系列(八):综合篇 - 在一张图中组合多种图表类型)
-
- [一、 为什么要组合图表?](#一、 为什么要组合图表?)
- [二、 核心技术与方法](#二、 核心技术与方法)
-
- [1. 使用多个Axes对象](#1. 使用多个Axes对象)
- [2. inset_axes - 创建图中图](#2. inset_axes - 创建图中图)
- [3. twinx() / twiny() - 双Y轴图表](#3. twinx() / twiny() - 双Y轴图表)
- [三、 实战案例:创建专业级组合图表](#三、 实战案例:创建专业级组合图表)
- [四、 最佳实践与设计原则](#四、 最佳实践与设计原则)
- [五、 常见陷阱与解决方案](#五、 常见陷阱与解决方案)
- [六、 总结](#六、 总结)
Matplotlib 可视化大师系列博客总览
本系列旨在提供一份系统、全面、深入的 Matplotlib 学习指南。以下是博客列表:
- 基础篇 :
plt.plot()
- 绘制折线图的利刃 - 分布篇 :
plt.scatter()
- 探索变量关系的散点图 - 比较篇 :
plt.bar()
与plt.barh()
- 清晰对比的柱状图 - 统计篇 :
plt.hist()
与plt.boxplot()
- 洞察数据分布 - 占比篇 :
plt.pie()
- 展示组成部分的饼图 - 高级篇 :
plt.imshow()
- 绘制矩阵与图像的强大工具 - 专属篇 : 绘制误差线 (
plt.errorbar()
)、等高线 (plt.contour()
) 等特殊图表 - 综合篇: 在一张图中组合多种图表类型
Matplotlib 可视化大师系列(八):综合篇 - 在一张图中组合多种图表类型
在前七篇文章中,我们已经掌握了Matplotlib中各种独立的图表类型。然而,真正强大的数据可视化往往需要将多种图表类型组合在一起,以揭示数据中更深层次的关系和模式。本文将深入探讨如何在一张图中有效地组合多种图表类型,创建信息丰富且易于理解的综合可视化。
一、 为什么要组合图表?
组合多种图表类型可以帮助我们:
- 提供多角度视角:同时展示数据的多个方面
- 建立关联:显示不同变量之间的关系
- 丰富上下文:为主图提供辅助信息和参考
- 节省空间:在有限区域内传达更多信息
- 讲述完整故事:通过多种视觉元素构建完整的数据叙事
二、 核心技术与方法
1. 使用多个Axes对象
这是最基本也是最强大的方法。通过plt.subplots()
或GridSpec
创建多个坐标轴,然后在每个坐标轴上绘制不同类型的图表。
python
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# 创建示例数据
np.random.seed(42)
dates = pd.date_range('2023-01-01', periods=100, freq='D')
main_data = np.cumsum(np.random.randn(100)) + 100
volume_data = np.random.randint(100, 1000, 100)
distribution_data = np.random.normal(0, 1, 1000)
fig = plt.figure(figsize=(14, 10))
# 使用GridSpec创建复杂布局
gs = plt.GridSpec(3, 4, figure=fig)
# 主图 - 时间序列
ax_main = fig.add_subplot(gs[0:2, 0:3])
ax_main.plot(dates, main_data, 'b-', linewidth=2, label='Price')
ax_main.set_title('Time Series with Multiple Chart Types')
ax_main.set_ylabel('Price')
ax_main.grid(True, alpha=0.3)
ax_main.legend()
# 交易量柱状图(共享x轴)
ax_volume = fig.add_subplot(gs[2, 0:3], sharex=ax_main)
ax_volume.bar(dates, volume_data, color='gray', alpha=0.7, width=0.8)
ax_volume.set_ylabel('Volume')
ax_volume.set_xlabel('Date')
# 分布直方图
ax_dist = fig.add_subplot(gs[0:2, 3])
ax_dist.hist(distribution_data, bins=30, orientation='horizontal',
alpha=0.7, color='green', edgecolor='black')
ax_dist.set_title('Return Distribution')
ax_dist.set_xlabel('Frequency')
# 相关性热力图
corr_matrix = np.random.rand(5, 5)
np.fill_diagonal(corr_matrix, 1)
ax_corr = fig.add_subplot(gs[2, 3])
im = ax_corr.imshow(corr_matrix, cmap='coolwarm', vmin=-1, vmax=1)
plt.colorbar(im, ax=ax_corr)
ax_corr.set_title('Correlation')
ax_corr.set_xticks(range(5))
ax_corr.set_yticks(range(5))
ax_corr.set_xticklabels(['A', 'B', 'C', 'D', 'E'])
ax_corr.set_yticklabels(['A', 'B', 'C', 'D', 'E'])
plt.tight_layout()
plt.show()
2. inset_axes - 创建图中图
对于需要在主图内部嵌入小图的场景,inset_axes
是最佳选择。
python
# 创建数据
x = np.linspace(0, 10, 100)
y_main = np.sin(x) * np.exp(-x/10)
y_detail = np.sin(x)
fig, ax = plt.subplots(figsize=(10, 6))
# 主图
ax.plot(x, y_main, 'b-', linewidth=2, label='Damped Oscillation')
ax.set_xlabel('Time')
ax.set_ylabel('Amplitude')
ax.set_title('Main Plot with Inset')
ax.grid(True, alpha=0.3)
ax.legend()
# 创建 inset axes 显示细节
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
ax_inset = inset_axes(ax, width="30%", height="30%", loc='upper right')
ax_inset.plot(x, y_detail, 'r-', linewidth=1)
ax_inset.set_title('Undamped Detail')
ax_inset.set_xticks([])
ax_inset.set_yticks([])
# 在inset中标记与主图的对应区域
ax_inset_indicator = inset_axes(ax, width="5%", height="5%", loc='lower left')
ax_inset_indicator.plot([0, 1], [0, 1], 'r-')
ax_inset_indicator.set_xticks([])
ax_inset_indicator.set_yticks([])
ax_inset_indicator.spines['top'].set_visible(False)
ax_inset_indicator.spines['right'].set_visible(False)
ax_inset_indicator.spines['bottom'].set_visible(False)
ax_inset_indicator.spines['left'].set_visible(False)
plt.tight_layout()
plt.show()
3. twinx() / twiny() - 双Y轴图表
当需要显示两个不同量纲但有关联的数据系列时,双Y轴非常有用。
python
# 创建数据
x = np.linspace(0, 10, 100)
y1 = np.sin(x) # 第一个数据系列
y2 = np.exp(x/3) # 第二个数据系列,不同量纲
fig, ax1 = plt.subplots(figsize=(10, 6))
# 第一个Y轴(左侧)
color = 'tab:blue'
ax1.set_xlabel('Time')
ax1.set_ylabel('Sine Wave', color=color)
line1 = ax1.plot(x, y1, color=color, linewidth=2, label='Sine Wave')
ax1.tick_params(axis='y', labelcolor=color)
ax1.grid(True, alpha=0.3)
# 创建第二个Y轴(右侧)
ax2 = ax1.twinx()
color = 'tab:red'
ax2.set_ylabel('Exponential Growth', color=color)
line2 = ax2.plot(x, y2, color=color, linewidth=2, linestyle='--', label='Exponential')
ax2.tick_params(axis='y', labelcolor=color)
# 添加图例(需要合并两个轴的线条)
lines = line1 + line2
labels = [l.get_label() for l in lines]
ax1.legend(lines, labels, loc='upper left')
plt.title('Dual Y-Axis Example')
plt.tight_layout()
plt.show()
三、 实战案例:创建专业级组合图表
案例1:金融仪表板
python
# 创建金融数据示例
np.random.seed(123)
dates = pd.date_range('2023-01-01', periods=60, freq='D')
prices = np.cumsum(np.random.randn(60)) + 100
volumes = np.random.randint(100, 2000, 60)
returns = np.diff(prices) / prices[:-1] * 100
fig = plt.figure(figsize=(16, 12))
gs = plt.GridSpec(3, 2, figure=fig, hspace=0.4, wspace=0.3)
# 1. 价格图表
ax_price = fig.add_subplot(gs[0, :])
ax_price.plot(dates, prices, 'b-', linewidth=2)
ax_price.set_title('Stock Price Movement', fontsize=14, fontweight='bold')
ax_price.set_ylabel('Price ($)')
ax_price.grid(True, alpha=0.3)
# 添加移动平均线
window = 20
moving_avg = pd.Series(prices).rolling(window=window).mean()
ax_price.plot(dates, moving_avg, 'r--', linewidth=1.5, label=f'{window}-day MA')
ax_price.legend()
# 2. 交易量图表
ax_volume = fig.add_subplot(gs[1, 0])
ax_volume.bar(dates, volumes, color='gray', alpha=0.7)
ax_volume.set_title('Trading Volume', fontsize=12)
ax_volume.set_ylabel('Volume')
ax_volume.grid(True, alpha=0.3)
# 3. 收益率分布
ax_returns = fig.add_subplot(gs[1, 1])
ax_returns.hist(returns, bins=20, color='orange', alpha=0.7, edgecolor='black')
ax_returns.set_title('Return Distribution', fontsize=12)
ax_returns.set_xlabel('Daily Return (%)')
ax_returns.set_ylabel('Frequency')
ax_returns.axvline(returns.mean(), color='red', linestyle='--', label=f'Mean: {returns.mean():.2f}%')
ax_returns.legend()
# 4. 相关性热力图
stocks = ['AAPL', 'GOOGL', 'MSFT', 'TSLA', 'AMZN']
corr_data = np.random.rand(5, 5)
np.fill_diagonal(corr_data, 1)
corr_data = (corr_data + corr_data.T) / 2 # 确保对称
ax_corr = fig.add_subplot(gs[2, 0])
im = ax_corr.imshow(corr_data, cmap='RdYlBu_r', vmin=-1, vmax=1)
plt.colorbar(im, ax=ax_corr)
ax_corr.set_title('Stock Correlation Matrix', fontsize=12)
ax_corr.set_xticks(range(5))
ax_corr.set_yticks(range(5))
ax_corr.set_xticklabels(stocks, rotation=45)
ax_corr.set_yticklabels(stocks)
# 5. 波动率图表
volatility = pd.Series(returns).rolling(window=20).std() * np.sqrt(252) * 100 # 年化波动率
ax_vol = fig.add_subplot(gs[2, 1])
ax_vol.plot(dates[20:], volatility[19:], 'purple', linewidth=2) # 调整日期匹配
ax_vol.set_title('20-day Historical Volatility', fontsize=12)
ax_vol.set_ylabel('Volatility (%)')
ax_vol.set_xlabel('Date')
ax_vol.grid(True, alpha=0.3)
ax_vol.fill_between(dates[20:], volatility[19:], alpha=0.3, color='purple')
plt.suptitle('Financial Dashboard - Comprehensive Market Analysis',
fontsize=16, fontweight='bold', y=0.98)
plt.tight_layout()
plt.show()
案例2:科学数据可视化
python
# 创建科学数据示例
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y) * np.exp(-0.1*(X**2 + Y**2))
# 计算梯度(用于矢量场)
grad_x = np.cos(X) * np.cos(Y) * np.exp(-0.1*(X**2 + Y**2)) - 0.2*X*np.sin(X)*np.cos(Y)*np.exp(-0.1*(X**2 + Y**2))
grad_y = -np.sin(X) * np.sin(Y) * np.exp(-0.1*(X**2 + Y**2)) - 0.2*Y*np.sin(X)*np.cos(Y)*np.exp(-0.1*(X**2 + Y**2))
fig = plt.figure(figsize=(15, 10))
gs = plt.GridSpec(2, 2, figure=fig, hspace=0.3, wspace=0.3)
# 1. 3D表面图
ax_3d = fig.add_subplot(gs[0, 0], projection='3d')
surf = ax_3d.plot_surface(X, Y, Z, cmap='viridis', alpha=0.8)
ax_3d.set_title('3D Surface Plot')
fig.colorbar(surf, ax=ax_3d, shrink=0.5)
# 2. 等高线图 + 梯度场
ax_contour = fig.add_subplot(gs[0, 1])
contour = ax_contour.contour(X, Y, Z, 20, colors='black', alpha=0.6)
ax_contour.clabel(contour, inline=True, fontsize=8)
ax_contour.quiver(X[::5, ::5], Y[::5, ::5], grad_x[::5, ::5], grad_y[::5, ::5],
scale=30, color='blue', alpha=0.7)
ax_contour.set_title('Contour Plot with Gradient Field')
ax_contour.set_xlabel('X')
ax_contour.set_ylabel('Y')
# 3. 剖面线分析
ax_profile = fig.add_subplot(gs[1, 0])
y_slice = 0 # 在y=0处取剖面
profile = Z[y_slice, :]
ax_profile.plot(x, profile, 'r-', linewidth=2)
ax_profile.set_title(f'Profile at Y = {y_slice}')
ax_profile.set_xlabel('X')
ax_contour.set_ylabel('Z')
ax_profile.grid(True, alpha=0.3)
# 标记剖面线在主图中的位置
ax_contour.axhline(y=y_slice, color='red', linestyle='--', alpha=0.7)
# 4. 统计分布
ax_hist = fig.add_subplot(gs[1, 1])
ax_hist.hist(Z.flatten(), bins=30, color='green', alpha=0.7, edgecolor='black')
ax_hist.set_title('Value Distribution')
ax_hist.set_xlabel('Z value')
ax_hist.set_ylabel('Frequency')
ax_hist.axvline(Z.mean(), color='red', linestyle='--',
label=f'Mean: {Z.mean():.3f}')
ax_hist.legend()
plt.suptitle('Multidimensional Scientific Data Analysis',
fontsize=16, fontweight='bold', y=0.95)
plt.tight_layout()
plt.show()
四、 最佳实践与设计原则
-
保持一致性:
- 使用统一的颜色方案
- 保持相同的字体和字号
- 确保坐标轴标签和刻度风格一致
-
创建视觉层次:
- 最重要的图表应该最突出
- 使用大小、位置和颜色创建层次感
- 辅助图表应该支持而不是分散注意力
-
有效利用空间:
- 使用
tight_layout()
或constrained_layout
避免元素重叠 - 合理利用边距和空白
- 考虑图表之间的相对大小和重要性
- 使用
-
提供清晰的导航:
- 使用标题和子标题引导读者
- 添加注释和箭头指出重要特征
- 确保图例清晰易懂
-
交互性考虑(如果适用):
- 对于复杂仪表板,考虑使用Plotly或Bokeh获得交互功能
- 添加工具提示、缩放和平移功能
五、 常见陷阱与解决方案
-
过度拥挤:
- 问题:太多图表挤在一起,难以阅读
- 解决方案:减少图表数量或增加图形尺寸;使用选项卡或交互式元素
-
不一致的样式:
- 问题:不同图表使用不同颜色方案和样式
- 解决方案:定义样式模板并在所有图表中一致使用
-
缺乏焦点:
- 问题:读者不知道首先看哪里
- 解决方案:使用视觉层次明确引导注意力;添加明确的标题和说明
-
性能问题:
- 问题:复杂组合图表渲染缓慢
- 解决方案:优化数据量;考虑使用静态图片导出或专业可视化库
六、 总结
组合多种图表类型是数据可视化的高级技能,可以极大地增强你的数据叙事能力。通过本文学到的技术,你可以:
- 使用多个Axes对象创建复杂的网格布局
- 使用inset_axes添加细节视图
- 使用**twinx()/twiny()**显示不同量纲的数据
- 遵循设计原则创建专业级的组合图表
- 避免常见陷阱,创建清晰有效的可视化
记住,最好的组合图表不是简单地堆砌多种图表类型,而是有目的地选择和组织视觉元素,以最有效的方式传达数据故事。每种添加的图表都应该有明确的目的和价值。
至此,我们已经完成了Matplotlib可视化大师系列的全部内容。从基础图表到高级组合技巧,你现在已经掌握了使用Matplotlib创建专业级数据可视化所需的全部技能。