Matplotlib 数据可视化
Matplotlib 是 Python 生态系统中最基础、最强大的数据可视化库,几乎所有其他可视化库(如 Seaborn、Pandas 绑图、Plotly 等)都构建在 Matplotlib 之上。掌握 Matplotlib 不仅能让你创建任何类型的图表,还能帮助你理解其他可视化库的工作原理。
Matplotlib 架构
Matplotlib 采用分层架构设计,理解这个架构对于正确使用 Matplotlib 非常重要:
三层架构说明:
| 层级 | 说明 | 典型使用场景 |
|---|---|---|
| 后端层 | 处理绘图设备的底层细节 | 很少直接使用 |
| 艺术家层 | 图表的核心对象模型 | 精细控制、复杂图表 |
| 脚本层 | 简化的 pyplot 接口 | 快速绑图、简单图表 |
核心对象关系
import matplotlib.pyplot as plt
# Figure:整个图形窗口,可以包含多个 Axes
fig = plt.figure()
# Axes:单个图表区域,包含坐标轴、标签、标题等
ax = fig.add_subplot()
# Axis:坐标轴,控制刻度、标签
xaxis = ax.xaxis
yaxis = ax.yaxis
两种绑图风格
Matplotlib 提供两种主要的绑图风格,各有优缺点:
pyplot 风格(MATLAB 风格)
适合快速探索性分析,语法简单直观:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
plt.figure(figsize=(10, 6))
plt.plot(x, np.sin(x), label='sin(x)')
plt.plot(x, np.cos(x), label='cos(x)')
plt.xlabel('x')
plt.ylabel('y')
plt.title('三角函数')
plt.legend()
plt.grid(True)
plt.show()
面向对象风格
适合复杂图表和精细控制,是专业用法:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
# 创建 Figure 和 Axes 对象
fig, ax = plt.subplots(figsize=(10, 6))
# 在 Axes 上绑图
ax.plot(x, np.sin(x), label='sin(x)')
ax.plot(x, np.cos(x), label='cos(x)')
# 设置 Axes 属性
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('三角函数')
ax.legend()
ax.grid(True)
plt.show()
为什么推荐面向对象风格?
- 更清晰的对象管理:明确知道操作的是哪个图表
- 更好的代码复用:可以轻松封装图表函数
- 支持复杂布局:多子图时不会混淆
- 与 Pandas/Seaborn 兼容:这些库返回的是 Axes 对象
基本图表类型
折线图(Line Plot)
折线图用于展示数据随时间或序列的变化趋势:
import matplotlib.pyplot as plt
import numpy as np
# 创建数据
x = np.linspace(0, 2 * np.pi, 100)
y1 = np.sin(x)
y2 = np.cos(x)
fig, ax = plt.subplots(figsize=(10, 6))
# 绘制多条线
ax.plot(x, y1, 'b-', linewidth=2, label='sin(x)') # 蓝色实线
ax.plot(x, y2, 'r--', linewidth=2, label='cos(x)') # 红色虚线
# 标记特殊点
ax.plot([np.pi/2], [1], 'go', markersize=10) # 标记最大值
ax.annotate('最大值', xy=(np.pi/2, 1), xytext=(np.pi/2 + 0.5, 0.8),
arrowprops=dict(arrowstyle='->', color='green'))
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('三角函数图像')
ax.legend(loc='upper right')
ax.grid(True, alpha=0.3)
ax.set_xlim(0, 2 * np.pi)
ax.set_ylim(-1.2, 1.2)
plt.tight_layout()
plt.show()
plot 函数常用参数:
| 参数 | 说明 | 示例 |
|---|---|---|
color / c | 颜色 | 'red', 'r', '#FF5733' |
linestyle / ls | 线型 | '-', '--', ':', '-.' |
linewidth / lw | 线宽 | 2.0 |
marker | 标记样式 | 'o', 's', '^', '+' |
markersize / ms | 标记大小 | 8 |
label | 图例标签 | '数据1' |
alpha | 透明度 | 0.5 |
格式字符串简写:'[color][marker][line]'
ax.plot(x, y, 'ro-') # 红色、圆标记、实线
ax.plot(x, y, 'g^--') # 绿色、三角标记、虚线
ax.plot(x, y, 'bs:') # 蓝色、方形标记、点线
散点图(Scatter Plot)
散点图用于展示两个变量之间的关系:
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(42)
n = 100
# 创建数据
x = np.random.randn(n)
y = x * 0.8 + np.random.randn(n) * 0.5
colors = np.random.randn(n)
sizes = np.random.uniform(50, 200, n)
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 简单散点图
axes[0].scatter(x, y, c='steelblue', alpha=0.6, s=60)
axes[0].set_xlabel('X')
axes[0].set_ylabel('Y')
axes[0].set_title('简单散点图')
axes[0].grid(True, alpha=0.3)
# 高级散点图(颜色和大小编码)
scatter = axes[1].scatter(x, y, c=colors, s=sizes,
cmap='viridis', alpha=0.7,
edgecolors='white', linewidth=0.5)
axes[1].set_xlabel('X')
axes[1].set_ylabel('Y')
axes[1].set_title('多维散点图')
fig.colorbar(scatter, ax=axes[1], label='颜色值')
plt.tight_layout()
plt.show()
柱状图(Bar Chart)
柱状图用于比较不同类别的数值:
import matplotlib.pyplot as plt
import numpy as np
categories = ['产品A', '产品B', '产品C', '产品D', '产品E']
values = [23, 45, 67, 34, 56]
values2 = [18, 52, 45, 40, 35]
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# 简单柱状图
bars = axes[0].bar(categories, values, color='steelblue', edgecolor='navy')
axes[0].set_xlabel('产品')
axes[0].set_ylabel('销量')
axes[0].set_title('简单柱状图')
axes[0].tick_params(axis='x', rotation=45)
# 添加数值标签
for bar in bars:
height = bar.get_height()
axes[0].annotate(f'{height}',
xy=(bar.get_x() + bar.get_width() / 2, height),
xytext=(0, 3), textcoords='offset points',
ha='center', va='bottom')
# 分组柱状图
x = np.arange(len(categories))
width = 0.35
bars1 = axes[1].bar(x - width/2, values, width, label='2023年', color='steelblue')
bars2 = axes[1].bar(x + width/2, values2, width, label='2024年', color='coral')
axes[1].set_xlabel('产品')
axes[1].set_ylabel('销量')
axes[1].set_title('分组柱状图')
axes[1].set_xticks(x)
axes[1].set_xticklabels(categories)
axes[1].legend()
axes[1].tick_params(axis='x', rotation=45)
# 横向柱状图
axes[2].barh(categories, values, color='teal', edgecolor='darkslategray')
axes[2].set_xlabel('销量')
axes[2].set_ylabel('产品')
axes[2].set_title('横向柱状图')
plt.tight_layout()
plt.show()
直方图(Histogram)
直方图用于展示数值变量的分布:
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(42)
# 生成不同分布的数据
data1 = np.random.normal(0, 1, 1000) # 正态分布
data2 = np.random.normal(2, 1.5, 1000) # 偏移正态分布
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# 单变量直方图
axes[0].hist(data1, bins=30, color='steelblue', edgecolor='white', alpha=0.7)
axes[0].set_xlabel('值')
axes[0].set_ylabel('频数')
axes[0].set_title('正态分布直方图')
axes[0].axvline(np.mean(data1), color='red', linestyle='--', label=f'均值: {np.mean(data1):.2f}')
axes[0].legend()
# 多变量对比
axes[1].hist(data1, bins=30, alpha=0.6, label='数据集1', color='steelblue')
axes[1].hist(data2, bins=30, alpha=0.6, label='数据集2', color='coral')
axes[1].set_xlabel('值')
axes[1].set_ylabel('频数')
axes[1].set_title('分布对比')
axes[1].legend()
plt.tight_layout()
plt.show()
饼图(Pie Chart)
饼图用于展示各部分占整体的比例:
import matplotlib.pyplot as plt
sizes = [35, 25, 20, 12, 8]
labels = ['产品A', '产品B', '产品C', '产品D', '其他']
colors = ['#3498db', '#e74c3c', '#2ecc71', '#f39c12', '#9b59b6']
explode = (0.05, 0, 0, 0, 0) # 突出显示第一块
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 基本饼图
axes[0].pie(sizes, labels=labels, colors=colors, autopct='%1.1f%%',
shadow=True, startangle=90)
axes[0].set_title('市场份额')
axes[0].axis('equal')
# 突出显示的饼图
wedges, texts, autotexts = axes[1].pie(sizes, labels=labels, colors=colors,
autopct='%1.1f%%', explode=explode,
startangle=90, pctdistance=0.85)
# 设置百分比文字样式
for autotext in autotexts:
autotext.set_color('white')
autotext.set_fontweight('bold')
axes[1].set_title('市场份额(突出显示)')
axes[1].axis('equal')
plt.tight_layout()
plt.show()
箱线图(Box Plot)
箱线图用于展示数据分布的五数概括:
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(42)
# 创建不同分布的数据
data = [np.random.normal(0, 1, 100),
np.random.normal(0, 1.5, 100),
np.random.normal(1, 1, 100),
np.random.normal(0.5, 0.8, 100)]
labels = ['组A', '组B', '组C', '组D']
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 基本箱线图
bp1 = axes[0].boxplot(data, labels=labels, patch_artist=True)
axes[0].set_ylabel('值')
axes[0].set_title('箱线图')
axes[0].grid(True, alpha=0.3, axis='y')
# 设置颜色
colors = ['lightblue', 'lightgreen', 'lightcoral', 'lightyellow']
for patch, color in zip(bp1['boxes'], colors):
patch.set_facecolor(color)
# 横向箱线图(显示均值)
bp2 = axes[1].boxplot(data, labels=labels, vert=False, patch_artist=True,
showmeans=True, meanline=True)
axes[1].set_xlabel('值')
axes[1].set_title('横向箱线图(显示均值)')
axes[1].grid(True, alpha=0.3, axis='x')
for patch, color in zip(bp2['boxes'], colors):
patch.set_facecolor(color)
plt.tight_layout()
plt.show()
箱线图解读:
最大值(或上限)
|
┌─────────┴─────────┐
│ │
│ 上四分位数(Q3) │───┐
│ │ │
│ 中位数 │ │ 四分位距(IQR)
│ │ │ = Q3 - Q1
│ 下四分位数(Q1) │───┘
│ │
└─────────┬─────────┘
|
最小值(或下限)
○ 离群点(超出 1.5*IQR 范围)
热力图(Heatmap)
热力图用颜色深浅表示数值大小:
import matplotlib.pyplot as plt
import numpy as np
# 创建相关性矩阵
np.random.seed(42)
data = np.random.randn(5, 5)
corr_matrix = np.corrcoef(data)
labels = ['变量A', '变量B', '变量C', '变量D', '变量E']
fig, ax = plt.subplots(figsize=(8, 6))
# 绘制热力图
im = ax.imshow(corr_matrix, cmap='RdYlGn', aspect='auto', vmin=-1, vmax=1)
# 设置刻度
ax.set_xticks(np.arange(len(labels)))
ax.set_yticks(np.arange(len(labels)))
ax.set_xticklabels(labels)
ax.set_yticklabels(labels)
# 旋转x轴标签
plt.setp(ax.get_xticklabels(), rotation=45, ha='right')
# 添加数值标注
for i in range(len(labels)):
for j in range(len(labels)):
text = ax.text(j, i, f'{corr_matrix[i, j]:.2f}',
ha='center', va='center', color='black')
# 添加颜色条
cbar = fig.colorbar(im, ax=ax)
cbar.set_label('相关系数')
ax.set_title('变量相关性矩阵')
plt.tight_layout()
plt.show()
子图布局
基本子图
使用 subplots 创建规则网格布局:
import matplotlib.pyplot as plt
import numpy as np
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
x = np.linspace(0, 10, 100)
# 左上:折线图
axes[0, 0].plot(x, np.sin(x), 'b-', label='sin')
axes[0, 0].plot(x, np.cos(x), 'r--', label='cos')
axes[0, 0].set_title('三角函数')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)
# 右上:散点图
axes[0, 1].scatter(np.random.rand(50), np.random.rand(50),
c=np.random.rand(50), cmap='viridis', alpha=0.7)
axes[0, 1].set_title('随机散点图')
# 左下:柱状图
categories = ['A', 'B', 'C', 'D']
values = [15, 30, 25, 20]
axes[1, 0].bar(categories, values, color=['#3498db', '#e74c3c', '#2ecc71', '#f39c12'])
axes[1, 0].set_title('柱状图')
# 右下:直方图
axes[1, 1].hist(np.random.randn(1000), bins=30, color='teal', edgecolor='white', alpha=0.7)
axes[1, 1].set_title('正态分布')
plt.tight_layout()
plt.show()
不规则布局
使用 GridSpec 创建不规则布局:
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
fig = plt.figure(figsize=(12, 8))
gs = GridSpec(3, 3, figure=fig)
# 顶部跨三列
ax1 = fig.add_subplot(gs[0, :])
ax1.plot([1, 2, 3], [1, 2, 1])
ax1.set_title('顶部面板(跨三列)')
# 中间左侧跨两行
ax2 = fig.add_subplot(gs[1:, 0])
ax2.bar(['A', 'B', 'C'], [3, 7, 5])
ax2.set_title('左侧面板(跨两行)')
# 中间右侧
ax3 = fig.add_subplot(gs[1, 1:])
ax3.scatter([1, 2, 3], [1, 3, 2])
ax3.set_title('中右面板')
# 底部右侧
ax4 = fig.add_subplot(gs[2, 1:])
ax4.pie([30, 40, 30], labels=['X', 'Y', 'Z'])
ax4.set_title('底右面板')
plt.tight_layout()
plt.show()
嵌入式子图
在主图中嵌入小图:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(figsize=(10, 6))
# 主图
x = np.linspace(0, 10, 100)
ax.plot(x, np.sin(x), 'b-', linewidth=2)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('主图')
# 嵌入小图(使用 add_axes)
ax_inset = fig.add_axes([0.55, 0.55, 0.3, 0.3]) # [left, bottom, width, height]
ax_inset.plot(x, np.sin(x), 'r-')
ax_inset.set_title('嵌入图')
ax_inset.set_xlim(0, 3) # 放大局部区域
plt.show()
3D 图表
Matplotlib 通过 mplot3d 工具包支持 3D 绑图。
创建 3D 图表
import matplotlib.pyplot as plt
import numpy as np
# 创建 3D 坐标系
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(projection='3d')
# 准备数据
z = np.linspace(0, 15, 100)
x = np.sin(z)
y = np.cos(z)
# 3D 线图
ax.plot(x, y, z, 'b-', linewidth=2)
# 设置标签
ax.set_xlabel('X轴')
ax.set_ylabel('Y轴')
ax.set_zlabel('Z轴')
ax.set_title('3D 螺旋线')
plt.tight_layout()
plt.show()
3D 散点图
import matplotlib.pyplot as plt
import numpy as np
np.random.seed(42)
n = 100
# 随机生成 3D 数据
x = np.random.randn(n)
y = np.random.randn(n)
z = np.random.randn(n)
colors = np.random.rand(n)
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(projection='3d')
# 3D 散点图
scatter = ax.scatter(x, y, z, c=colors, cmap='viridis',
s=50, alpha=0.6, edgecolors='white')
ax.set_xlabel('X轴')
ax.set_ylabel('Y轴')
ax.set_zlabel('Z轴')
ax.set_title('3D 散点图')
fig.colorbar(scatter, ax=ax, shrink=0.5, label='颜色值')
plt.tight_layout()
plt.show()
3D 曲面图
import matplotlib.pyplot as plt
import numpy as np
# 创建网格数据
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
# 计算 Z 值(二维高斯分布)
Z = np.sin(np.sqrt(X**2 + Y**2))
fig = plt.figure(figsize=(14, 5))
# 曲面图
ax1 = fig.add_subplot(121, projection='3d')
surf = ax1.plot_surface(X, Y, Z, cmap='coolwarm', alpha=0.8)
ax1.set_xlabel('X')
ax1.set_ylabel('Y')
ax1.set_zlabel('Z')
ax1.set_title('3D 曲面图')
fig.colorbar(surf, ax=ax1, shrink=0.5)
# 线框图
ax2 = fig.add_subplot(122, projection='3d')
ax2.plot_wireframe(X, Y, Z, color='steelblue', linewidth=0.5, rstride=5, cstride=5)
ax2.set_xlabel('X')
ax2.set_ylabel('Y')
ax2.set_zlabel('Z')
ax2.set_title('线框图')
plt.tight_layout()
plt.show()
3D 等高线图
import matplotlib.pyplot as plt
import numpy as np
# 创建数据
x = np.linspace(-3, 3, 100)
y = np.linspace(-3, 3, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y)
fig = plt.figure(figsize=(14, 5))
# 3D 等高线图
ax1 = fig.add_subplot(121, projection='3d')
ax1.contour3D(X, Y, Z, 50, cmap='viridis')
ax1.set_xlabel('X')
ax1.set_ylabel('Y')
ax1.set_zlabel('Z')
ax1.set_title('3D 等高线图')
# 填充等高线图
ax2 = fig.add_subplot(122, projection='3d')
ax2.contourf3D(X, Y, Z, 50, cmap='viridis')
ax2.set_xlabel('X')
ax2.set_ylabel('Y')
ax2.set_zlabel('Z')
ax2.set_title('填充等高线图')
plt.tight_layout()
plt.show()
3D 柱状图
import matplotlib.pyplot as plt
import numpy as np
# 创建数据
x = np.arange(5)
y = np.arange(4)
X, Y = np.meshgrid(x, y)
X = X.flatten()
Y = Y.flatten()
# 随机高度
np.random.seed(42)
Z = np.random.randint(1, 10, len(X))
# 柱子位置
dx = dy = 0.8
dz = Z
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(projection='3d')
# 3D 柱状图
ax.bar3d(X, Y, np.zeros_like(Z), dx, dy, dz,
color='steelblue', alpha=0.8, edgecolor='navy')
ax.set_xlabel('X轴')
ax.set_ylabel('Y轴')
ax.set_zlabel('Z轴')
ax.set_title('3D 柱状图')
plt.tight_layout()
plt.show()
视角控制
import matplotlib.pyplot as plt
import numpy as np
# 创建数据
x = np.linspace(-5, 5, 50)
y = np.linspace(-5, 5, 50)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))
fig = plt.figure(figsize=(14, 10))
# 不同视角
angles = [(30, 45), (60, 45), (30, 135), (90, 0)]
titles = ['视角1: elev=30, azim=45', '视角2: elev=60, azim=45',
'视角3: elev=30, azim=135', '视角4: elev=90, azim=0']
for i, (elev, azim) in enumerate(angles):
ax = fig.add_subplot(2, 2, i + 1, projection='3d')
ax.plot_surface(X, Y, Z, cmap='coolwarm', alpha=0.8)
ax.view_init(elev=elev, azim=azim) # 设置视角
ax.set_title(titles[i])
plt.tight_layout()
plt.show()
视角参数说明:
elev(仰角):从水平面向上看的角度,范围 -180 到 180azim(方位角):绕 Z 轴旋转的角度,范围 0 到 360
动画
Matplotlib 的 animation 模块支持创建动态图表。
FuncAnimation 基础
FuncAnimation 通过重复调用更新函数来创建动画:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
fig, ax = plt.subplots(figsize=(10, 6))
# 初始化数据
x = np.linspace(0, 2 * np.pi, 100)
line, = ax.plot(x, np.sin(x), 'b-', linewidth=2)
ax.set_xlim(0, 2 * np.pi)
ax.set_ylim(-1.5, 1.5)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_title('正弦波动画')
ax.grid(True, alpha=0.3)
# 更新函数
def update(frame):
# 更新 y 数据
line.set_ydata(np.sin(x + frame / 10))
return line,
# 创建动画
ani = animation.FuncAnimation(fig, update, frames=100,
interval=50, blit=True)
plt.tight_layout()
plt.show()
FuncAnimation 关键参数:
| 参数 | 说明 |
|---|---|
fig | Figure 对象 |
func | 更新函数,接收帧号参数 |
frames | 帧数或帧序列 |
interval | 帧间隔(毫秒) |
blit | 是否只重绘变化部分(提高性能) |
repeat | 是否循环播放 |
粒子动画示例
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
# 初始化粒子位置
np.random.seed(42)
n_particles = 50
x = np.random.uniform(0, 10, n_particles)
y = np.random.uniform(0, 10, n_particles)
vx = np.random.uniform(-0.1, 0.1, n_particles)
vy = np.random.uniform(-0.1, 0.1, n_particles)
fig, ax = plt.subplots(figsize=(10, 8))
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.set_aspect('equal')
ax.set_title('粒子运动模拟')
# 创建散点图
scat = ax.scatter(x, y, c='steelblue', s=50, alpha=0.7)
def update(frame):
global x, y, vx, vy
# 更新位置
x += vx
y += vy
# 边界反弹
mask_x = (x < 0) | (x > 10)
mask_y = (y < 0) | (y > 10)
vx[mask_x] *= -1
vy[mask_y] *= -1
# 更新散点图数据
scat.set_offsets(np.column_stack([x, y]))
return scat,
ani = animation.FuncAnimation(fig, update, frames=200,
interval=50, blit=True)
plt.tight_layout()
plt.show()
柱状图动画
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
# 初始数据
categories = ['A', 'B', 'C', 'D', 'E']
values = np.random.randint(10, 50, len(categories))
fig, ax = plt.subplots(figsize=(10, 6))
ax.set_ylim(0, 80)
ax.set_title('动态柱状图')
# 创建柱状图
bars = ax.bar(categories, values, color='steelblue', edgecolor='navy')
def update(frame):
# 更新数据
new_values = bars.datavalues + np.random.randint(-5, 6, len(categories))
new_values = np.clip(new_values, 5, 75)
# 更新柱状图
for bar, val in zip(bars, new_values):
bar.set_height(val)
return bars
ani = animation.FuncAnimation(fig, update, frames=100,
interval=100, blit=True)
plt.tight_layout()
plt.show()
ArtistAnimation
ArtistAnimation 预先创建所有帧,适合复杂图表:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
fig, ax = plt.subplots(figsize=(10, 6))
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.set_title('ArtistAnimation 示例')
artists = []
x = np.linspace(0, 10, 100)
for i in range(20):
# 每帧创建新的线条
line, = ax.plot(x, np.sin(x + i * 0.3), 'b-', linewidth=2)
artists.append([line])
ani = animation.ArtistAnimation(fig, artists, interval=100, blit=True)
plt.tight_layout()
plt.show()
保存动画
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.animation as animation
fig, ax = plt.subplots(figsize=(10, 6))
x = np.linspace(0, 2 * np.pi, 100)
line, = ax.plot(x, np.sin(x))
ax.set_ylim(-1.5, 1.5)
def update(frame):
line.set_ydata(np.sin(x + frame / 10))
return line,
ani = animation.FuncAnimation(fig, update, frames=60, interval=50)
# 保存为 GIF(需要 Pillow)
# ani.save('animation.gif', writer='pillow', fps=15)
# 保存为 MP4(需要 ffmpeg)
# ani.save('animation.mp4', writer='ffmpeg', fps=15)
# 保存为 HTML(嵌入网页)
# html = ani.to_jshtml()
# with open('animation.html', 'w') as f:
# f.write(html)
plt.show()
样式定制
内置样式
Matplotlib 提供多种内置样式:
import matplotlib.pyplot as plt
import numpy as np
# 查看所有可用样式
print(plt.style.available)
# 常用样式:
# 'seaborn-v0_8-whitegrid' - 白底网格
# 'ggplot' - R语言ggplot风格
# 'dark_background' - 深色背景
# 'fivethirtyeight' - FiveThirtyEight网站风格
# 'bmh' - Bayesian Methods for Hackers风格
# 使用样式
plt.style.use('seaborn-v0_8-whitegrid')
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(x, np.sin(x), label='sin(x)')
ax.plot(x, np.cos(x), label='cos(x)')
ax.set_title('使用 seaborn-whitegrid 样式')
ax.legend()
plt.show()
# 恢复默认样式
plt.style.use('default')
临时样式
使用上下文管理器临时应用样式:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
# 临时使用深色主题
with plt.style.context('dark_background'):
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(x, np.sin(x), 'c-', label='sin(x)')
ax.set_title('临时深色主题')
ax.legend()
plt.show()
# 退出后恢复原样式
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(x, np.sin(x))
ax.set_title('恢复原样式')
plt.show()
rcParams 配置
通过 rcParams 全局配置图表属性:
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
# 方法1:直接修改 rcParams
mpl.rcParams['figure.figsize'] = (10, 6)
mpl.rcParams['font.size'] = 12
mpl.rcParams['axes.labelsize'] = 14
mpl.rcParams['axes.titlesize'] = 16
mpl.rcParams['lines.linewidth'] = 2
mpl.rcParams['lines.markersize'] = 8
mpl.rcParams['axes.grid'] = True
mpl.rcParams['grid.alpha'] = 0.3
# 方法2:使用 rc 函数批量设置
mpl.rc('axes', labelsize=14, titlesize=16)
mpl.rc('lines', linewidth=2, markersize=8)
# 方法3:使用字典更新
params = {
'figure.figsize': (10, 6),
'axes.labelsize': 14,
'axes.titlesize': 16,
'font.size': 12,
}
mpl.rcParams.update(params)
# 绘图测试
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots()
ax.plot(x, np.sin(x))
ax.set_xlabel('X轴')
ax.set_ylabel('Y轴')
ax.set_title('rcParams 配置测试')
plt.show()
# 恢复默认设置
mpl.rcParams.update(mpl.rcParamsDefault)
自定义样式文件
创建 .mplstyle 文件定义自定义样式:
# 创建自定义样式文件
# 文件内容示例 (my_style.mplstyle):
"""
figure.figsize: 12, 8
figure.dpi: 100
axes.labelsize: 14
axes.titlesize: 16
axes.titleweight: bold
axes.grid: True
axes.facecolor: white
axes.edgecolor: black
axes.linewidth: 1.5
lines.linewidth: 2.5
lines.markersize: 10
font.family: sans-serif
font.size: 12
xtick.labelsize: 12
ytick.labelsize: 12
legend.fontsize: 12
legend.frameon: True
grid.alpha: 0.3
"""
# 使用自定义样式文件
# plt.style.use('path/to/my_style.mplstyle')
组合样式
import matplotlib.pyplot as plt
import numpy as np
# 组合多个样式(后面覆盖前面)
plt.style.use(['seaborn-v0_8-whitegrid', 'dark_background'])
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(x, np.sin(x))
ax.set_title('组合样式')
plt.show()
plt.style.use('default')
中文字体配置
Matplotlib 默认不支持中文显示,需要配置字体。
临时配置(推荐)
import matplotlib.pyplot as plt
import numpy as np
# 方法1:设置全局字体(临时)
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'SimSun']
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(x, np.sin(x))
ax.set_title('中文标题测试')
ax.set_xlabel('横坐标')
ax.set_ylabel('纵坐标')
plt.show()
永久配置
修改 matplotlibrc 配置文件:
import matplotlib
import matplotlib.pyplot as plt
# 查找配置文件位置
config_file = matplotlib.matplotlib_fname()
print(f'配置文件位置: {config_file}')
# 修改配置文件中的以下项:
# font.family: sans-serif
# font.sans-serif: SimHei, Microsoft YaHei, DejaVu Sans
# axes.unicode_minus: False
字体管理
import matplotlib.font_manager as fm
import matplotlib.pyplot as plt
# 查看所有可用字体
fonts = sorted(set([f.name for f in fm.fontManager.ttflist]))
print('可用字体数量:', len(fonts))
# 搜索中文字体
chinese_fonts = [f for f in fonts if any(k in f for k in ['SimHei', 'SimSun', 'Microsoft', 'Hei', 'Song', 'Kai', 'Fang'])]
print('中文字体:', chinese_fonts[:10])
# 使用指定字体
plt.rcParams['font.sans-serif'] = ['SimHei']
# 或者直接指定字体文件
font_path = 'C:/Windows/Fonts/simhei.ttf' # Windows 字体路径
prop = fm.FontProperties(fname=font_path)
fig, ax = plt.subplots()
ax.set_title('使用指定字体', fontproperties=prop, fontsize=16)
plt.show()
不同系统字体配置
import matplotlib.pyplot as plt
import platform
system = platform.system()
if system == 'Windows':
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
elif system == 'Darwin': # macOS
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS', 'PingFang SC']
else: # Linux
plt.rcParams['font.sans-serif'] = ['WenQuanYi Micro Hei', 'Noto Sans CJK SC']
plt.rcParams['axes.unicode_minus'] = False
图表导出
基本保存
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(x, np.sin(x))
ax.set_title('导出测试')
# 保存为 PNG
fig.savefig('figure.png', dpi=300, bbox_inches='tight')
# 保存为 PDF(矢量图,适合论文)
fig.savefig('figure.pdf', bbox_inches='tight')
# 保存为 SVG(矢量图,网页使用)
fig.savefig('figure.svg', bbox_inches='tight')
# 保存为 JPG(需要设置质量)
fig.savefig('figure.jpg', dpi=300, quality=95, bbox_inches='tight')
# 透明背景
fig.savefig('figure_transparent.png', transparent=True, dpi=300)
plt.show()
保存参数说明:
| 参数 | 说明 | 推荐值 |
|---|---|---|
dpi | 分辨率 | 屏幕:100,印刷:300 |
bbox_inches | 裁剪边界 | 'tight' |
transparent | 透明背景 | True |
quality | JPG 质量 | 95 |
facecolor | 背景色 | 'white' |
edgecolor | 边框色 | 'none' |
输出为代码
生成 HTML 嵌入代码:
import matplotlib.pyplot as plt
import numpy as np
import base64
from io import BytesIO
x = np.linspace(0, 10, 100)
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(x, np.sin(x))
ax.set_title('Base64 嵌入测试')
# 转换为 Base64
buffer = BytesIO()
fig.savefig(buffer, format='png', dpi=100, bbox_inches='tight')
buffer.seek(0)
img_base64 = base64.b64encode(buffer.getvalue()).decode()
# 生成 HTML
html = f'<img src="data:image/png;base64,{img_base64}" />'
print(html)
plt.close()
实战示例
示例:多维度数据可视化报告
import matplotlib.pyplot as plt
import numpy as np
import matplotlib as mpl
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
# 设置样式
plt.style.use('seaborn-v0_8-whitegrid')
# 创建模拟数据
np.random.seed(42)
n_students = 50
students = [f'学生{i+1}' for i in range(n_students)]
chinese = np.random.randint(60, 100, n_students)
math = np.random.randint(50, 100, n_students)
english = np.random.randint(55, 98, n_students)
total = chinese + math + english
# 创建综合报告图表
fig = plt.figure(figsize=(16, 12))
# 1. 成绩分布直方图
ax1 = fig.add_subplot(2, 3, 1)
ax1.hist([chinese, math, english], bins=15, label=['语文', '数学', '英语'],
alpha=0.7, edgecolor='white')
ax1.set_xlabel('分数')
ax1.set_ylabel('人数')
ax1.set_title('成绩分布')
ax1.legend()
# 2. 科目相关性散点图
ax2 = fig.add_subplot(2, 3, 2)
scatter = ax2.scatter(math, english, c=chinese, cmap='RdYlGn',
s=50, alpha=0.7, edgecolors='white')
ax2.set_xlabel('数学成绩')
ax2.set_ylabel('英语成绩')
ax2.set_title('数学vs英语(颜色=语文)')
plt.colorbar(scatter, ax=ax2, label='语文成绩')
# 3. 科目对比箱线图
ax3 = fig.add_subplot(2, 3, 3)
bp = ax3.boxplot([chinese, math, english], labels=['语文', '数学', '英语'],
patch_artist=True)
colors = ['#3498db', '#e74c3c', '#2ecc71']
for patch, color in zip(bp['boxes'], colors):
patch.set_facecolor(color)
ax3.set_ylabel('分数')
ax3.set_title('科目成绩对比')
# 4. 总分分布
ax4 = fig.add_subplot(2, 3, 4)
ax4.hist(total, bins=20, color='teal', edgecolor='white', alpha=0.7)
ax4.axvline(np.mean(total), color='red', linestyle='--',
label=f'平均分: {np.mean(total):.0f}')
ax4.set_xlabel('总分')
ax4.set_ylabel('人数')
ax4.set_title('总分分布')
ax4.legend()
# 5. 前10名学生
ax5 = fig.add_subplot(2, 3, 5)
top10_idx = np.argsort(total)[-10:][::-1]
top10_names = [students[i] for i in top10_idx]
top10_scores = total[top10_idx]
colors = plt.cm.RdYlGn(np.linspace(0.3, 0.9, 10))
ax5.barh(range(10), top10_scores, color=colors)
ax5.set_yticks(range(10))
ax5.set_yticklabels(top10_names)
ax5.set_xlabel('总分')
ax5.set_title('总分前10名')
# 6. 科目雷达图
ax6 = fig.add_subplot(2, 3, 6, projection='polar')
categories = ['语文', '数学', '英语']
N = len(categories)
# 计算各科平均分
avg_scores = [np.mean(chinese), np.mean(math), np.mean(english)]
max_scores = [100, 100, 100]
normalized = [s/m * 100 for s, m in zip(avg_scores, max_scores)]
# 闭合雷达图
angles = [n / float(N) * 2 * np.pi for n in range(N)]
angles += angles[:1]
normalized += normalized[:1]
ax6.plot(angles, normalized, 'o-', linewidth=2)
ax6.fill(angles, normalized, alpha=0.25)
ax6.set_xticks(angles[:-1])
ax6.set_xticklabels(categories)
ax6.set_title('各科平均分雷达图')
plt.suptitle('学生成绩综合分析报告', fontsize=16, fontweight='bold', y=1.02)
plt.tight_layout()
plt.show()
# 恢复默认样式
plt.style.use('default')
示例:时间序列可视化
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
plt.rcParams['axes.unicode_minus'] = False
# 创建模拟股票数据
np.random.seed(42)
dates = pd.date_range('2024-01-01', periods=100)
prices = 100 + np.cumsum(np.random.randn(100) * 2)
volumes = np.random.randint(1000000, 5000000, 100)
# 创建双 Y 轴图表
fig, ax1 = plt.subplots(figsize=(14, 8))
# 价格线图
color = 'tab:blue'
ax1.plot(dates, prices, color=color, linewidth=1.5, label='股价')
ax1.fill_between(dates, prices.min() - 10, prices, alpha=0.1, color=color)
ax1.set_xlabel('日期')
ax1.set_ylabel('股价(元)', color=color)
ax1.tick_params(axis='y', labelcolor=color)
ax1.set_title('股票价格与成交量')
# 成交量柱状图(次Y轴)
ax2 = ax1.twinx()
colors = ['green' if prices[i] > prices[i-1] else 'red' for i in range(1, len(prices))]
colors = ['gray'] + colors
ax2.bar(dates, volumes, color=colors, alpha=0.3, width=0.8)
ax2.set_ylabel('成交量', color='gray')
ax2.tick_params(axis='y', labelcolor='gray')
# 添加移动平均线
ma5 = pd.Series(prices).rolling(window=5).mean()
ma20 = pd.Series(prices).rolling(window=20).mean()
ax1.plot(dates, ma5, 'r--', linewidth=1, alpha=0.7, label='MA5')
ax1.plot(dates, ma20, 'g--', linewidth=1, alpha=0.7, label='MA20')
ax1.legend(loc='upper left')
plt.tight_layout()
plt.show()
小结
本章我们系统学习了 Matplotlib 数据可视化:
- 架构理解:三层架构(后端层、艺术家层、脚本层)和两种绑图风格
- 基本图表:折线图、散点图、柱状图、直方图、饼图、箱线图、热力图
- 子图布局:subplots、GridSpec、嵌入式子图
- 3D 图表:线图、散点图、曲面图、等高线图、柱状图、视角控制
- 动画:FuncAnimation、ArtistAnimation、保存动画
- 样式定制:内置样式、rcParams、自定义样式文件
- 中文支持:临时配置、永久配置、跨平台方案
- 图表导出:PNG、PDF、SVG、Base64 嵌入
Matplotlib 是 Python 可视化的基础,掌握了它,你就能够创建任何类型的图表。
练习
- 创建一个包含 4 个子图的图表,分别展示折线图、散点图、柱状图和饼图
- 使用面向对象风格创建一个 3D 曲面图,并尝试不同的视角
- 创建一个动画,展示正弦波的传播过程
- 配置自定义样式,使图表具有统一的风格
参考资源
下一步
现在你已经掌握了 Matplotlib 的核心功能。如果你想创建更美观的统计图表,可以学习 Seaborn 数据可视化,它提供了更高级的接口和更美观的默认样式!