跳到主要内容

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()

为什么推荐面向对象风格?

  1. 更清晰的对象管理:明确知道操作的是哪个图表
  2. 更好的代码复用:可以轻松封装图表函数
  3. 支持复杂布局:多子图时不会混淆
  4. 与 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 到 180
  • azim(方位角):绕 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 关键参数

参数说明
figFigure 对象
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
qualityJPG 质量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 数据可视化:

  1. 架构理解:三层架构(后端层、艺术家层、脚本层)和两种绑图风格
  2. 基本图表:折线图、散点图、柱状图、直方图、饼图、箱线图、热力图
  3. 子图布局:subplots、GridSpec、嵌入式子图
  4. 3D 图表:线图、散点图、曲面图、等高线图、柱状图、视角控制
  5. 动画:FuncAnimation、ArtistAnimation、保存动画
  6. 样式定制:内置样式、rcParams、自定义样式文件
  7. 中文支持:临时配置、永久配置、跨平台方案
  8. 图表导出:PNG、PDF、SVG、Base64 嵌入

Matplotlib 是 Python 可视化的基础,掌握了它,你就能够创建任何类型的图表。

练习

  1. 创建一个包含 4 个子图的图表,分别展示折线图、散点图、柱状图和饼图
  2. 使用面向对象风格创建一个 3D 曲面图,并尝试不同的视角
  3. 创建一个动画,展示正弦波的传播过程
  4. 配置自定义样式,使图表具有统一的风格

参考资源

下一步

现在你已经掌握了 Matplotlib 的核心功能。如果你想创建更美观的统计图表,可以学习 Seaborn 数据可视化,它提供了更高级的接口和更美观的默认样式!