一、Matplotlib 架构与核心概念
1.1 三层架构体系
用户层 (pyplot) ← 最简单,面向MATLAB用户 ↓ 艺术家层 (Artist) ← 面向对象,更精细的控制 ↓ 后端层 (Backend) ← 底层渲染
1.2 关键术语
Figure:画布,可以包含多个子图
Axes:坐标系,真正绘图的地方
Axis:坐标轴
Artist:所有可见元素
Backend:渲染引擎
二、快速入门
2.1 基本绘图流程
import matplotlib.pyplot as plt import numpy as np # 1. 准备数据 x = np.linspace(0, 10, 100) y = np.sin(x) # 2. 创建图形和坐标轴 fig, ax = plt.subplots(figsize=(10, 6)) # 3. 绘制数据 ax.plot(x, y, label='sin(x)', color='blue', linewidth=2, linestyle='-') # 4. 自定义图表 ax.set_title('正弦函数', fontsize=16, fontweight='bold') ax.set_xlabel('X轴', fontsize=12) ax.set_ylabel('Y轴', fontsize=12) ax.legend() ax.grid(True, alpha=0.3) # 5. 显示或保存 plt.tight_layout() plt.savefig('sine_wave.png', dpi=300, bbox_inches='tight') plt.show()三、图形与坐标轴控制
3.1 创建多子图
# 方法1:subplot plt.figure(figsize=(12, 8)) # 2行2列,第1个子图 plt.subplot(2, 2, 1) plt.plot(x, np.sin(x)) plt.title('sin(x)') plt.subplot(2, 2, 2) plt.plot(x, np.cos(x)) plt.title('cos(x)') # 方法2:subplots (推荐) fig, axes = plt.subplots( nrows=2, ncols=3, figsize=(15, 10), sharex=True, # 共享x轴 sharey=True # 共享y轴 ) # 遍历所有坐标轴 for i, ax in enumerate(axes.flat): ax.plot(x, np.sin(x + i*0.5)) ax.set_title(f'Plot {i+1}') ax.grid(True) plt.tight_layout() # 方法3:GridSpec (更灵活布局) fig = plt.figure(figsize=(12, 8)) gs = fig.add_gridspec(3, 3) # 3行3列网格 # 创建不同大小的子图 ax1 = fig.add_subplot(gs[0, :]) # 第1行,所有列 ax2 = fig.add_subplot(gs[1, :2]) # 第2行,前2列 ax3 = fig.add_subplot(gs[1:, 2]) # 第1行到最后,第3列 ax4 = fig.add_subplot(gs[2, 0]) # 第3行,第1列 ax5 = fig.add_subplot(gs[2, 1]) # 第3行,第2列3.2 坐标轴控制
fig, ax = plt.subplots(1, 2, figsize=(12, 5)) # 左图:基本设置 ax[0].plot(x, np.tan(x)) ax[0].set_xlim(0, 10) # 设置x轴范围 ax[0].set_ylim(-5, 5) # 设置y轴范围 ax[0].set_xlabel('X轴', fontsize=12) ax[0].set_ylabel('Y轴', fontsize=12) ax[0].set_title('tan(x)函数', fontsize=14) ax[0].grid(True, linestyle='--', alpha=0.7) # 右图:高级设置 ax[1].plot(x, np.exp(x)) ax[1].set_xscale('log') # 对数坐标 ax[1].set_yscale('log') ax[1].set_xticks([1, 2, 5, 10]) # 自定义刻度 ax[1].set_xticklabels(['一', '二', '五', '十'], fontsize=10) # 自定义标签 ax[1].tick_params(axis='both', which='major', labelsize=10) ax[1].spines['top'].set_visible(False) # 隐藏顶部边框 ax[1].spines['right'].set_visible(False) # 隐藏右边框 # 双y轴 fig, ax1 = plt.subplots(figsize=(10, 6)) ax1.plot(x, np.sin(x), 'b-', label='sin(x)') ax1.set_xlabel('X轴') ax1.set_ylabel('sin(x)', color='b') ax1.tick_params(axis='y', labelcolor='b') ax2 = ax1.twinx() # 共享x轴 ax2.plot(x, np.cos(x), 'r-', label='cos(x)') ax2.set_ylabel('cos(x)', color='r') ax2.tick_params(axis='y', labelcolor='r') # 添加图例 lines1, labels1 = ax1.get_legend_handles_labels() lines2, labels2 = ax2.get_legend_handles_labels() ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper right')四、绘图类型大全
4.1 基本图表类型
fig, axes = plt.subplots(2, 3, figsize=(15, 10)) x = np.linspace(0, 10, 20) y = np.random.randn(20) # 1. 折线图 axes[0, 0].plot(x, y, marker='o', linestyle='-', color='blue', markersize=8) axes[0, 0].set_title('折线图') # 2. 散点图 axes[0, 1].scatter(x, y, c=y, cmap='viridis', s=100, alpha=0.7, edgecolors='black') axes[0, 1].set_title('散点图') # 3. 柱状图 categories = ['A', 'B', 'C', 'D', 'E'] values = [23, 45, 56, 78, 33] axes[0, 2].bar(categories, values, color=['red', 'blue', 'green', 'orange', 'purple']) axes[0, 2].set_title('柱状图') # 4. 水平柱状图 axes[1, 0].barh(categories, values, color='skyblue') axes[1, 0].set_title('水平柱状图') # 5. 直方图 data = np.random.randn(1000) axes[1, 1].hist(data, bins=30, edgecolor='black', alpha=0.7, color='lightgreen') axes[1, 1].set_title('直方图') # 6. 饼图 sizes = [15, 30, 45, 10] labels = ['A', 'B', 'C', 'D'] axes[1, 2].pie(sizes, labels=labels, autopct='%1.1f%%', explode=(0, 0.1, 0, 0), shadow=True) axes[1, 2].set_title('饼图') plt.tight_layout()4.2 高级图表类型
fig = plt.figure(figsize=(16, 12)) # 1. 箱线图 plt.subplot(2, 3, 1) data = [np.random.normal(0, std, 100) for std in range(1, 4)] plt.boxplot(data, labels=['A', 'B', 'C'], patch_artist=True, boxprops=dict(facecolor='lightblue')) plt.title('箱线图') # 2. 小提琴图 plt.subplot(2, 3, 2) import seaborn as sns sns.violinplot(data=data) plt.title('小提琴图') # 3. 热力图 plt.subplot(2, 3, 3) matrix = np.random.rand(10, 10) plt.imshow(matrix, cmap='hot', interpolation='nearest') plt.colorbar() plt.title('热力图') # 4. 等高线图 plt.subplot(2, 3, 4) X, Y = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100)) Z = np.sin(np.sqrt(X**2 + Y**2)) plt.contourf(X, Y, Z, 20, cmap='RdYlBu') plt.colorbar() plt.title('等高线图') # 5. 3D图 from mpl_toolkits.mplot3d import Axes3D ax = fig.add_subplot(2, 3, 5, projection='3d') X, Y = np.meshgrid(np.linspace(-5, 5, 50), np.linspace(-5, 5, 50)) Z = np.sin(np.sqrt(X**2 + Y**2)) ax.plot_surface(X, Y, Z, cmap='viridis', edgecolor='none') ax.set_title('3D曲面图') # 6. 极坐标图 plt.subplot(2, 3, 6, projection='polar') theta = np.linspace(0, 2*np.pi, 100) r = 2 + np.cos(5*theta) plt.plot(theta, r) plt.title('极坐标图') plt.tight_layout()五、样式与美化
5.1 内置样式
# 查看所有可用样式 print(plt.style.available) # 应用样式 plt.style.use('seaborn-v0_8-darkgrid') # 深色网格背景 # plt.style.use('ggplot') # R语言风格 # plt.style.use('fivethirtyeight') # 538风格 # plt.style.use('dark_background') # 黑色背景 # 恢复默认样式 plt.style.use('default') # 组合多个样式 plt.style.use(['seaborn-v0_8-whitegrid', 'seaborn-v0_8-poster'])5.2 颜色管理
# 1. 使用颜色名称 colors = ['red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'black'] # 2. 使用十六进制颜色 hex_colors = ['#FF5733', '#33FF57', '#3357FF', '#F3FF33'] # 3. 使用RGB/RGBA rgb_colors = [(0.2, 0.4, 0.6), (0.8, 0.2, 0.2, 0.5)] # RGBA支持透明度 # 4. 使用颜色映射 x = np.random.rand(100) y = np.random.rand(100) sizes = np.random.rand(100) * 1000 colors = np.random.rand(100) plt.figure(figsize=(12, 5)) # 内置颜色映射 plt.subplot(1, 2, 1) scatter = plt.scatter(x, y, s=sizes, c=colors, cmap='viridis', alpha=0.6) plt.colorbar(scatter) plt.title('viridis颜色映射') # 反转颜色映射 plt.subplot(1, 2, 2) scatter = plt.scatter(x, y, s=sizes, c=colors, cmap='viridis_r', alpha=0.6) plt.colorbar(scatter) plt.title('反转颜色映射') plt.tight_layout() # 常用颜色映射 # 顺序型: viridis, plasma, summer, winter, hot, cool # 发散型: RdYlBu, RdYlGn, bwr, coolwarm # 分类型: tab10, Set1, Set2, Set3, tab205.3 字体与文本
fig, ax = plt.subplots(figsize=(10, 6)) # 绘制数据 x = np.linspace(0, 10, 100) ax.plot(x, np.sin(x), label='正弦波') ax.plot(x, np.cos(x), label='余弦波') # 文本设置 ax.set_title('三角函数可视化', fontsize=18, fontweight='bold', fontfamily='Microsoft YaHei', # 中文字体 pad=20) # 与图的间距 ax.set_xlabel('角度 (弧度)', fontsize=14, style='italic') ax.set_ylabel('函数值', fontsize=14, style='italic') # 自定义刻度标签字体 ax.tick_params(axis='both', labelsize=12) # 图例文本 ax.legend(fontsize=12, frameon=True, shadow=True, fancybox=True) # 添加注解 ax.annotate('最大值点', xy=(np.pi/2, 1), # 箭头指向的点 xytext=(3, 1.5), # 文本位置 arrowprops=dict(arrowstyle='->', connectionstyle='arc3', color='red'), fontsize=12, bbox=dict(boxstyle='round,pad=0.5', facecolor='yellow', alpha=0.3)) # 添加文本 ax.text(2, 0.5, '这里是一个重要的\n注释', fontsize=10, bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5)) # 数学公式 ax.text(7, 0, r'$\int_0^\infty e^{-x^2} dx = \frac{\sqrt{\pi}}{2}$', fontsize=14, bbox=dict(boxstyle='round', facecolor='lightblue', alpha=0.5))六、高级功能
6.1 动画
import matplotlib.animation as animation from IPython.display import HTML # 创建动画 fig, ax = plt.subplots(figsize=(8, 6)) x = np.linspace(0, 2*np.pi, 100) line, = ax.plot(x, np.sin(x)) ax.grid(True) # 动画函数 def animate(frame): line.set_ydata(np.sin(x + frame/10.0)) # 更新数据 return line, # 创建动画对象 ani = animation.FuncAnimation(fig, animate, frames=100, interval=50, blit=True) # 保存为GIF ani.save('sine_wave.gif', writer='pillow', fps=20) # 在Jupyter中显示 # HTML(ani.to_html5_video())6.2 交互式图表
# 启用交互模式 plt.ion() fig, ax = plt.subplots(figsize=(10, 6)) x = np.linspace(0, 10, 100) for phase in np.linspace(0, 2*np.pi, 20): ax.clear() # 清除之前的内容 ax.plot(x, np.sin(x + phase)) ax.set_title(f'Phase: {phase:.2f}') ax.grid(True) plt.draw() # 重新绘制 plt.pause(0.1) # 暂停一下 plt.ioff() # 关闭交互模式6.3 自定义图形元素
# 创建自定义标记 from matplotlib.path import Path from matplotlib.markers import MarkerStyle # 自定义路径 star_vertices = [ (0, 1), (-0.3, 0.2), (-1, 0.2), (-0.5, -0.2), (-0.6, -1), (0, -0.6), (0.6, -1), (0.5, -0.2), (1, 0.2), (0.3, 0.2), (0, 1) ] star_codes = [Path.MOVETO] + [Path.LINETO] * 9 + [Path.CLOSEPOLYO] star_path = Path(star_vertices, star_codes) star_marker = MarkerStyle(marker=star_path) fig, ax = plt.subplots(figsize=(8, 6)) x = np.random.rand(20) y = np.random.rand(20) ax.scatter(x, y, s=200, marker=star_marker, color='gold', edgecolor='black')七、性能优化
7.1 大数据集优化
# 1. 使用set_data而不是重新绘图 fig, ax = plt.subplots(figsize=(10, 6)) x = np.linspace(0, 10, 10000) y = np.random.randn(10000) # 高效方式 line, = ax.plot([], [], '-', alpha=0.5) # 先创建空线条 line.set_data(x, y) # 然后设置数据 ax.set_xlim(x.min(), x.max()) ax.set_ylim(y.min(), y.max()) # 2. 降低分辨率 # 对大数据集进行下采样 def downsample(data, factor): return data[::factor] x_down = downsample(x, 10) y_down = downsample(y, 10) # 3. 使用rasterized fig, ax = plt.subplots(figsize=(10, 6)) scatter = ax.scatter(x, y, s=1, alpha=0.5, rasterized=True) # 栅格化7.2 保存优化
# 保存高质量图片 fig.savefig('high_quality.png', dpi=300, # 分辨率 bbox_inches='tight', # 去除白边 facecolor='white', # 背景色 edgecolor='none', # 边框颜色 transparent=False, # 是否透明 pad_inches=0.1) # 内边距 # 保存为矢量图 fig.savefig('vector.svg', format='svg') fig.savefig('vector.pdf', format='pdf') # 批量保存 for i in range(5): fig, ax = plt.subplots() ax.plot(np.random.rand(10)) ax.set_title(f'Figure {i+1}') fig.savefig(f'figure_{i+1}.png', dpi=150) plt.close(fig) # 关闭图形释放内存八、实战项目模板
8.1 科学论文图表
def create_scientific_plot(): # 设置科学论文风格 plt.rcParams.update({ 'font.size': 12, 'axes.labelsize': 14, 'axes.titlesize': 16, 'xtick.labelsize': 12, 'ytick.labelsize': 12, 'legend.fontsize': 12, 'figure.titlesize': 18, 'lines.linewidth': 2, 'axes.grid': True, 'grid.alpha': 0.3, 'savefig.dpi': 300, 'savefig.bbox': 'tight', 'savefig.format': 'pdf' }) # 创建图形 fig, axes = plt.subplots(2, 2, figsize=(12, 10)) fig.suptitle('实验结果可视化', fontweight='bold') # 子图1:时间序列 t = np.linspace(0, 10, 1000) data1 = np.sin(2*np.pi*t) + np.random.normal(0, 0.1, 1000) data2 = np.cos(2*np.pi*t) + np.random.normal(0, 0.1, 1000) axes[0, 0].plot(t, data1, label='实验组', color='#2E86AB') axes[0, 0].plot(t, data2, label='对照组', color='#A23B72', linestyle='--') axes[0, 0].set_xlabel('时间 (s)') axes[0, 0].set_ylabel('信号强度') axes[0, 0].legend() axes[0, 0].set_title('A. 时间序列分析') # 子图2:箱线图比较 groups = [np.random.normal(i, 0.5, 100) for i in range(4)] bp = axes[0, 1].boxplot(groups, labels=['A', 'B', 'C', 'D'], patch_artist=True) # 自定义箱线图颜色 colors = ['#F18F01', '#048BA8', '#2E933C', '#A23B72'] for patch, color in zip(bp['boxes'], colors): patch.set_facecolor(color) patch.set_alpha(0.7) axes[0, 1].set_ylabel('测量值') axes[0, 1].set_title('B. 组间比较') # 子图3:相关性热图 data = np.random.randn(100, 8) corr_matrix = np.corrcoef(data.T) im = axes[1, 0].imshow(corr_matrix, cmap='coolwarm', vmin=-1, vmax=1) plt.colorbar(im, ax=axes[1, 0]) # 添加相关系数值 for i in range(8): for j in range(8): axes[1, 0].text(j, i, f'{corr_matrix[i, j]:.2f}', ha='center', va='center', color='white' if abs(corr_matrix[i, j]) > 0.5 else 'black', fontsize=8) axes[1, 0].set_title('C. 变量相关性') axes[1, 0].set_xticks(range(8)) axes[1, 0].set_yticks(range(8)) axes[1, 0].set_xticklabels([f'V{i+1}' for i in range(8)]) axes[1, 0].set_yticklabels([f'V{i+1}' for i in range(8)]) # 子图4:3D散点图 ax3d = fig.add_subplot(2, 2, 4, projection='3d') x = np.random.randn(100) y = np.random.randn(100) z = np.random.randn(100) c = np.random.rand(100) scatter = ax3d.scatter(x, y, z, c=c, cmap='viridis', s=50, alpha=0.7) ax3d.set_xlabel('X轴') ax3d.set_ylabel('Y轴') ax3d.set_zlabel('Z轴') ax3d.set_title('D. 三维分布') plt.colorbar(scatter, ax=ax3d, shrink=0.7) plt.tight_layout() plt.savefig('scientific_plot.pdf') plt.show() create_scientific_plot()8.2 商业仪表板
def create_business_dashboard(): plt.style.use('seaborn-v0_8-darkgrid') fig = plt.figure(figsize=(18, 12)) gs = fig.add_gridspec(3, 4, hspace=0.3, wspace=0.3) # 1. 关键指标卡片 ax1 = fig.add_subplot(gs[0, 0]) metrics = { '收入': 125.6, '利润': 45.2, '增长率': 15.8, '市场份额': 22.4 } bars = ax1.barh(list(metrics.keys()), list(metrics.values()), color=['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4']) ax1.set_xlabel('(百万)') ax1.set_title('关键绩效指标', fontweight='bold') # 在条形图上添加数值 for bar, value in zip(bars, metrics.values()): width = bar.get_width() ax1.text(width + 2, bar.get_y() + bar.get_height()/2, f'{value:.1f}M', va='center', fontweight='bold') # 2. 月度销售趋势 ax2 = fig.add_subplot(gs[0, 1:3]) months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] sales = [120, 135, 150, 145, 160, 175, 190, 185, 200, 210, 220, 230] ax2.plot(months, sales, marker='o', linewidth=3, color='#2E86AB') ax2.fill_between(months, sales, alpha=0.3, color='#2E86AB') ax2.set_title('月度销售额趋势', fontweight='bold') ax2.set_ylabel('销售额 (百万)') ax2.grid(True, alpha=0.5) # 3. 产品销售占比 ax3 = fig.add_subplot(gs[0, 3]) products = ['产品A', '产品B', '产品C', '产品D', '其他'] market_share = [35, 25, 20, 15, 5] wedges, texts, autotexts = ax3.pie(market_share, labels=products, autopct='%1.1f%%', startangle=90, colors=['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7']) ax3.set_title('产品销售占比', fontweight='bold') # 4. 地区分布热图 ax4 = fig.add_subplot(gs[1, :]) regions = ['华北', '华东', '华南', '华西', '华中'] quarters = ['Q1', 'Q2', 'Q3', 'Q4'] data = np.random.randint(50, 100, size=(len(regions), len(quarters))) im = ax4.imshow(data, cmap='YlOrRd') # 显示数值 for i in range(len(regions)): for j in range(len(quarters)): ax4.text(j, i, f'{data[i, j]}', ha='center', va='center', color='white' if data[i, j] > 75 else 'black', fontweight='bold') ax4.set_xticks(range(len(quarters))) ax4.set_yticks(range(len(regions))) ax4.set_xticklabels(quarters) ax4.set_yticklabels(regions) ax4.set_title('各地区季度销售额', fontweight='bold') plt.colorbar(im, ax=ax4, label='销售额 (万)') # 5. 客户满意度 ax5 = fig.add_subplot(gs[2, :2]) categories = ['产品质量', '服务质量', '交付速度', '价格竞争力', '技术支持'] scores = [4.2, 4.5, 4.0, 3.8, 4.3] angles = np.linspace(0, 2*np.pi, len(categories), endpoint=False) scores = np.concatenate((scores, [scores[0]])) angles = np.concatenate((angles, [angles[0]])) ax5 = plt.subplot(gs[2, :2], projection='polar') ax5.plot(angles, scores, 'o-', linewidth=2) ax5.fill(angles, scores, alpha=0.25) ax5.set_xticks(angles[:-1]) ax5.set_xticklabels(categories) ax5.set_ylim(0, 5) ax5.set_title('客户满意度雷达图', fontweight='bold', pad=20) # 6. 目标完成进度 ax6 = fig.add_subplot(gs[2, 2:]) targets = ['销售目标', '利润目标', '增长目标', '客户目标'] achieved = [85, 92, 78, 88] bars = ax6.barh(targets, achieved, color='#4ECDC4') ax6.set_xlim(0, 100) ax6.set_xlabel('完成度 (%)') ax6.set_title('年度目标完成进度', fontweight='bold') # 添加进度条和百分比 for bar, value in zip(bars, achieved): width = bar.get_width() ax6.text(width/2, bar.get_y() + bar.get_height()/2, f'{value}%', va='center', ha='center', color='white', fontweight='bold') ax6.axvline(100, color='red', linestyle='--', alpha=0.5) plt.suptitle('商业智能仪表板', fontsize=20, fontweight='bold', y=0.98) plt.tight_layout() plt.savefig('business_dashboard.png', dpi=300, bbox_inches='tight') plt.show() create_business_dashboard()九、调试与常见问题
常见错误与解决方案
# 1. 中文显示问题 plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei', 'DejaVu Sans'] plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题 # 2. 图形显示不正常 fig, ax = plt.subplots(figsize=(10, 6)) # ... 绘图代码 ... plt.tight_layout() # 自动调整布局 plt.show() # 3. 保存图片不完整 fig.savefig('output.png', bbox_inches='tight', dpi=300) # 4. 性能优化 # 对于大数据集 x_large = np.random.randn(1000000) y_large = np.random.randn(1000000) # 方法1:下采样 fig, ax = plt.subplots() ax.plot(x_large[::1000], y_large[::1000], '.', markersize=1, alpha=0.5) # 方法2:使用hexbin fig, ax = plt.subplots() hb = ax.hexbin(x_large, y_large, gridsize=50, cmap='viridis') plt.colorbar(hb)十、最佳实践总结
统一风格:在整个项目中使用一致的样式
明确标签:为每个图表添加清晰的标题和轴标签
合适配色:使用色盲友好的颜色方案
避免杂乱:不要过度装饰,保持简洁
正确缩放:根据数据特点选择合适的坐标轴范围
添加图例:多数据系列时一定要添加图例
导出设置:根据用途设置合适的分辨率和格式
代码复用:将常用图表封装为函数
好的可视化应该让数据自己说话,而不是让图表技巧喧宾夺主。