Matplotlib 数据可视化
大约 14 分钟约 4240 字
Matplotlib 数据可视化
简介
Matplotlib 是 Python 最经典的数据可视化库,支持折线图、柱状图、散点图、饼图等几乎所有常见图表类型。它是数据分析和科学计算的标配可视化工具。
Matplotlib 的架构分为三层:(1) 后端层(Backend)——负责将图表渲染到屏幕或文件,支持 TkAgg、Qt5Agg、Agg(非交互式)等多种后端;(2) 艺术家层(Artist)——所有可见元素都是 Artist 对象,包括 Figure、Axes、Line2D、Text 等;(3) 脚本层(Pyplot)——matplotlib.pyplot 模块提供类似 MATLAB 的状态机接口,是最常用的编程接口。理解这三层关系有助于在需要精细控制时直接操作 Artist 对象。
特点
全局配置与中文字体
import matplotlib.pyplot as plt
import matplotlib as mpl
# 方式一:全局 rcParams 设置(影响所有图表)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 中文字体
plt.rcParams['axes.unicode_minus'] = False # 正常显示负号
plt.rcParams['figure.dpi'] = 100 # 默认 DPI
plt.rcParams['figure.figsize'] = (8, 6) # 默认尺寸
plt.rcParams['savefig.dpi'] = 150 # 保存 DPI
plt.rcParams['savefig.bbox'] = 'tight' # 保存时自动裁剪
plt.rcParams['axes.grid'] = True # 默认显示网格
plt.rcParams['grid.alpha'] = 0.3 # 网格透明度
# 方式二:使用样式表(推荐)
# 查看所有可用样式
print(plt.style.available)
# 使用内置样式
plt.style.use('seaborn-v0_8-whitegrid') # 类 Seaborn 风格
# 方式三:创建自定义样式文件
# 在项目目录下创建 my_style.mplstyle
# 内容示例:
# figure.figsize: 10, 6
# axes.titlesize: 14
# axes.labelsize: 12
# font.sans-serif: SimHei, Microsoft YaHei
# axes.unicode_minus: False
# lines.linewidth: 2
# axes.grid: True
# grid.alpha: 0.3
# 然后使用: plt.style.use('my_style.mplstyle')
# 方式四:上下文管理器临时切换样式
with plt.style.context('ggplot'):
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 9])
plt.title('临时使用 ggplot 样式')
plt.savefig('ggplot_temp.png')
plt.close()
# 重置为默认样式
plt.style.use('default')基础图表
折线图
import matplotlib.pyplot as plt
import numpy as np
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 折线图
months = ['1月', '2月', '3月', '4月', '5月', '6月']
sales = [120, 150, 180, 165, 200, 220]
costs = [80, 95, 110, 100, 130, 140]
plt.figure(figsize=(10, 6))
plt.plot(months, sales, 'b-o', label='销售额', linewidth=2, markersize=8)
plt.plot(months, costs, 'r--s', label='成本', linewidth=2, markersize=8)
plt.fill_between(months, sales, costs, alpha=0.2, color='green')
plt.title('月度销售额与成本对比', fontsize=16)
plt.xlabel('月份', fontsize=12)
plt.ylabel('金额(万元)', fontsize=12)
plt.legend(fontsize=12)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('line_chart.png', dpi=150)
plt.show()深入折线图:多轴与注释
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.ticker import FuncFormatter
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 双 Y 轴折线图
fig, ax1 = plt.subplots(figsize=(12, 6))
months = np.arange(1, 13)
revenue = [120, 135, 150, 165, 180, 210, 230, 225, 240, 260, 280, 310]
users = [1000, 1200, 1500, 1800, 2200, 2600, 3000, 3200, 3500, 4000, 4500, 5000]
# 左 Y 轴:收入
color1 = '#4472C4'
ax1.set_xlabel('月份', fontsize=12)
ax1.set_ylabel('收入(万元)', color=color1, fontsize=12)
line1 = ax1.plot(months, revenue, color=color1, marker='o', linewidth=2,
label='收入', markersize=6)
ax1.tick_params(axis='y', labelcolor=color1)
ax1.set_ylim(0, 400)
# 右 Y 轴:用户数
ax2 = ax1.twinx()
color2 = '#ED7D31'
ax2.set_ylabel('用户数', color=color2, fontsize=12)
line2 = ax2.plot(months, users, color=color2, marker='s', linewidth=2,
linestyle='--', label='用户数', markersize=6)
ax2.tick_params(axis='y', labelcolor=color2)
ax2.yaxis.set_major_formatter(FuncFormatter(lambda x, p: f'{x/1000:.0f}K'))
# 合并图例
lines = line1 + line2
labels = [l.get_label() for l in lines]
ax1.legend(lines, labels, loc='upper left')
# 添加注释
ax1.annotate('收入增长拐点',
xy=(6, 210), xytext=(4, 300),
arrowprops=dict(arrowstyle='->', color='red'),
fontsize=10, color='red')
plt.title('月度收入与用户增长趋势', fontsize=16)
plt.tight_layout()
plt.savefig('dual_axis.png', dpi=150)
plt.close()柱状图
# 柱状图
categories = ['技术部', '产品部', '设计部', '市场部']
q1 = [45, 30, 25, 35]
q2 = [50, 35, 28, 40]
x = np.arange(len(categories))
width = 0.35
fig, ax = plt.subplots(figsize=(10, 6))
bars1 = ax.bar(x - width/2, q1, width, label='Q1', color='#4472C4')
bars2 = ax.bar(x + width/2, q2, width, label='Q2', color='#ED7D31')
# 添加数值标签
for bar in bars1:
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
f'{bar.get_height()}', ha='center', fontsize=10)
for bar in bars2:
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
f'{bar.get_height()}', ha='center', fontsize=10)
ax.set_xlabel('部门')
ax.set_ylabel('人数')
ax.set_title('各部门季度人数对比')
ax.set_xticks(x)
ax.set_xticklabels(categories)
ax.legend()
plt.tight_layout()
plt.savefig('bar_chart.png', dpi=150)高级柱状图:水平柱状图与堆叠
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 水平柱状图(适合长标签)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
products = ['智能手机', '笔记本电脑', '平板电脑', '智能手表', '耳机']
sales = [8500, 6200, 3800, 2100, 4500]
# 水平柱状图
sorted_idx = np.argsort(sales)
ax1.barh([products[i] for i in sorted_idx],
[sales[i] for i in sorted_idx],
color=plt.cm.Blues(np.linspace(0.3, 0.9, len(products))))
ax1.set_xlabel('销售额(万元)')
ax1.set_title('产品销售额排行')
# 堆叠柱状图
quarters = ['Q1', 'Q2', 'Q3', 'Q4']
online = [120, 150, 180, 200]
offline = [80, 70, 60, 50]
x = np.arange(len(quarters))
ax2.bar(x, online, 0.5, label='线上', color='#4472C4')
ax2.bar(x, offline, 0.5, bottom=online, label='线下', color='#ED7D31')
ax2.set_xticks(x)
ax2.set_xticklabels(quarters)
ax2.set_ylabel('销售额(万元)')
ax2.set_title('线上线下销售额对比')
ax2.legend()
# 在堆叠柱上添加总值标签
for i in range(len(quarters)):
total = online[i] + offline[i]
ax2.text(i, total + 5, f'{total}', ha='center', fontsize=10)
plt.tight_layout()
plt.savefig('bar_advanced.png', dpi=150)
plt.close()散点图
# 散点图
np.random.seed(42)
x = np.random.randn(100)
y = 2 * x + np.random.randn(100) * 0.5
categories = np.random.choice(['A', 'B', 'C'], 100)
colors = {'A': 'red', 'B': 'blue', 'C': 'green'}
fig, ax = plt.subplots(figsize=(8, 6))
for cat in ['A', 'B', 'C']:
mask = categories == cat
ax.scatter(x[mask], y[mask], c=colors[cat], label=f'类别{cat}',
alpha=0.6, edgecolors='white', s=100)
# 趋势线
z = np.polyfit(x, y, 1)
p = np.poly1d(z)
x_line = np.linspace(x.min(), x.max(), 100)
ax.plot(x_line, p(x_line), 'k--', alpha=0.5, label='趋势线')
ax.set_xlabel('X 值')
ax.set_ylabel('Y 值')
ax.set_title('散点图与趋势线')
ax.legend()
plt.tight_layout()
plt.savefig('scatter_chart.png', dpi=150)高级散点图:气泡图
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
np.random.seed(42)
# 气泡图 —— 第三维度用气泡大小表示
countries = ['中国', '美国', '日本', '德国', '英国', '法国', '韩国', '印度']
gdp = [17.7, 25.5, 4.2, 4.1, 3.1, 2.8, 1.7, 3.4] # 万亿美元
population = [14.1, 3.3, 1.3, 0.83, 0.67, 0.68, 0.52, 14.2] # 亿人
life_exp = [78.2, 77.2, 84.8, 81.2, 81.3, 82.5, 83.7, 70.8]
fig, ax = plt.subplots(figsize=(12, 8))
# 气泡大小与人口成正比
sizes = [p * 80 for p in population]
scatter = ax.scatter(gdp, life_exp, s=sizes, c=population, cmap='YlOrRd',
alpha=0.7, edgecolors='gray', linewidth=1)
# 添加国家标签
for i, country in enumerate(countries):
ax.annotate(country, (gdp[i], life_exp[i]),
textcoords="offset points", xytext=(10, 5), fontsize=9)
ax.set_xlabel('GDP(万亿美元)', fontsize=12)
ax.set_ylabel('人均寿命(岁)', fontsize=12)
ax.set_title('各国 GDP、人口与人均寿命', fontsize=16)
fig.colorbar(scatter, ax=ax, label='人口(亿人)')
plt.tight_layout()
plt.savefig('bubble_chart.png', dpi=150)
plt.close()高级图表
饼图和热力图
# 饼图
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
# 饼图
sizes = [35, 25, 20, 15, 5]
labels = ['技术', '产品', '设计', '市场', '其他']
colors = ['#4472C4', '#ED7D31', '#A5A5A5', '#FFC000', '#5B9BD5']
explode = (0.05, 0, 0, 0, 0)
ax1.pie(sizes, explode=explode, labels=labels, colors=colors,
autopct='%1.1f%%', startangle=90, textprops={'fontsize': 11})
ax1.set_title('部门人数占比')
# 热力图(相关矩阵)
import pandas as pd
data = pd.DataFrame({
'销售额': np.random.randn(100) * 100 + 500,
'广告费': np.random.randn(100) * 20 + 50,
'访客数': np.random.randn(100) * 500 + 2000,
'转化率': np.random.rand(100) * 0.1 + 0.02
})
corr = data.corr()
im = ax2.imshow(corr, cmap='RdBu_r', vmin=-1, vmax=1)
ax2.set_xticks(range(len(corr)))
ax2.set_yticks(range(len(corr)))
ax2.set_xticklabels(corr.columns, rotation=45)
ax2.set_yticklabels(corr.columns)
# 添加数值标注
for i in range(len(corr)):
for j in range(len(corr)):
ax2.text(j, i, f'{corr.iloc[i, j]:.2f}',
ha='center', va='center', fontsize=10)
ax2.set_title('特征相关性热力图')
fig.colorbar(im, ax=ax2, shrink=0.8)
plt.tight_layout()
plt.savefig('advanced_charts.png', dpi=150)直方图与箱线图
import matplotlib.pyplot as plt
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
np.random.seed(42)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
# 直方图
data_a = np.random.normal(50, 10, 1000)
data_b = np.random.normal(60, 15, 1000)
ax1.hist(data_a, bins=30, alpha=0.6, color='#4472C4', label='组A', edgecolor='white')
ax1.hist(data_b, bins=30, alpha=0.6, color='#ED7D31', label='组B', edgecolor='white')
ax1.set_xlabel('数值')
ax1.set_ylabel('频数')
ax1.set_title('两组数据分布对比')
ax1.legend()
# 箱线图
data_box = [
np.random.normal(100, 15, 200),
np.random.normal(90, 20, 200),
np.random.normal(110, 10, 200),
np.random.normal(95, 25, 200),
]
labels_box = ['产品A', '产品B', '产品C', '产品D']
bp = ax2.boxplot(data_box, labels=labels_box, patch_artist=True,
medianprops=dict(color='red', linewidth=2))
colors_box = ['#4472C4', '#ED7D31', '#A5A5A5', '#FFC000']
for patch, color in zip(bp['boxes'], colors_box):
patch.set_facecolor(color)
patch.set_alpha(0.7)
ax2.set_ylabel('评分')
ax2.set_title('各产品评分分布')
plt.tight_layout()
plt.savefig('hist_box.png', dpi=150)
plt.close()多子图布局
# 2x2 子图布局
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
x = np.linspace(0, 2 * np.pi, 100)
# 子图1:正弦波
axes[0, 0].plot(x, np.sin(x), 'b-', linewidth=2)
axes[0, 0].set_title('正弦波')
axes[0, 0].grid(True, alpha=0.3)
# 子图2:柱状图
data = np.random.randn(20)
axes[0, 1].bar(range(len(data)), data, color=np.where(data > 0, 'green', 'red'))
axes[0, 1].set_title('正负柱状图')
# 子图3:直方图
normal_data = np.random.randn(1000)
axes[1, 0].hist(normal_data, bins=30, color='steelblue', edgecolor='white')
axes[1, 0].set_title('正态分布直方图')
# 子图4:面积图
x_area = np.arange(12)
y1 = np.random.randint(10, 50, 12)
y2 = np.random.randint(5, 30, 12)
axes[1, 1].stackplot(x_area, y1, y2, labels=['产品A', '产品B'],
colors=['#4472C4', '#ED7D31'], alpha=0.7)
axes[1, 1].set_title('堆叠面积图')
axes[1, 1].legend(loc='upper left')
plt.suptitle('多图表组合展示', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.savefig('subplots.png', dpi=150)高级子图布局:GridSpec
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
fig = plt.figure(figsize=(14, 10))
gs = gridspec.GridSpec(3, 3, figure=fig, hspace=0.4, wspace=0.3)
# 大图占据上方两列
ax_main = fig.add_subplot(gs[0:2, 0:2])
x = np.linspace(0, 10, 200)
ax_main.plot(x, np.sin(x), 'b-', linewidth=2, label='sin(x)')
ax_main.plot(x, np.cos(x), 'r--', linewidth=2, label='cos(x)')
ax_main.fill_between(x, np.sin(x), alpha=0.1, color='blue')
ax_main.set_title('主图表区域', fontsize=14)
ax_main.legend()
# 右上角小图
ax_top_right = fig.add_subplot(gs[0, 2])
data = np.random.randn(50)
ax_top_right.hist(data, bins=15, color='steelblue', edgecolor='white')
ax_top_right.set_title('数据分布')
# 右中角小图
ax_mid_right = fig.add_subplot(gs[1, 2])
sizes = [30, 25, 20, 15, 10]
ax_mid_right.pie(sizes, labels=['A', 'B', 'C', 'D', 'E'],
autopct='%1.0f%%', textprops={'fontsize': 8})
ax_mid_right.set_title('占比分布')
# 底部横跨三列
ax_bottom = fig.add_subplot(gs[2, :])
x_bar = ['1月', '2月', '3月', '4月', '5月', '6月']
y_bar = [100, 120, 150, 130, 180, 200]
ax_bottom.bar(x_bar, y_bar, color='#4472C4', alpha=0.8)
ax_bottom.set_title('月度趋势', fontsize=12)
plt.suptitle('GridSpec 复杂布局示例', fontsize=16, fontweight='bold')
plt.savefig('gridspec.png', dpi=150)
plt.close()面向对象绘图详解
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from matplotlib.axes import Axes
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 面向对象方式 vs pyplot 方式
# 推荐:面向对象方式,适合复杂图表和函数封装
# 方式一:pyplot(简单但不够灵活)
plt.figure()
plt.plot([1, 2, 3], [1, 4, 9])
plt.title('pyplot 方式')
plt.close()
# 方式二:面向对象(推荐)
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 9])
ax.set_title('面向对象方式')
plt.close()
# 封装绘图函数
def create_comparison_chart(
data: dict,
title: str,
xlabel: str,
ylabel: str,
figsize: tuple = (10, 6),
save_path: str | None = None,
) -> Figure:
"""创建对比折线图的通用函数
Args:
data: 键为系列名,值为 y 值列表的字典
title: 图表标题
xlabel: X 轴标签
ylabel: Y 轴标签
figsize: 图表尺寸
save_path: 保存路径(可选)
Returns:
Figure 对象
"""
fig, ax = plt.subplots(figsize=figsize)
colors = ['#4472C4', '#ED7D31', '#A5A5A5', '#FFC000', '#5B9BD5']
markers = ['o', 's', '^', 'D', 'v']
for idx, (name, values) in enumerate(data.items()):
x = range(len(values))
ax.plot(x, values,
color=colors[idx % len(colors)],
marker=markers[idx % len(markers)],
linewidth=2, markersize=6, label=name)
ax.set_title(title, fontsize=14)
ax.set_xlabel(xlabel, fontsize=12)
ax.set_ylabel(ylabel, fontsize=12)
ax.legend()
ax.grid(True, alpha=0.3)
if save_path:
plt.savefig(save_path, dpi=150, bbox_inches='tight')
return fig
# 使用
chart_data = {
'销售额': [120, 150, 180, 200, 220],
'利润': [30, 40, 55, 65, 75],
}
fig = create_comparison_chart(chart_data, '销售趋势', '月份', '万元')
plt.close()样式与颜色系统
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# Matplotlib 颜色表示方式
# 1. 单字符颜色: 'b', 'r', 'g', 'c', 'm', 'y', 'k', 'w'
# 2. HTML 颜色: '#4472C4', 'red', 'blue'
# 3. RGB 元组: (0.2, 0.5, 0.8)
# 4. RGBA 元组: (0.2, 0.5, 0.8, 0.5)
# 5. 灰度字符串: '0.5' (50% 灰)
# 查看命名颜色
print(f"命名颜色数量: {len(mcolors.CSS4_COLORS)}")
# Colormap(颜色映射)
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
x = np.linspace(0, 10, 100)
y = np.sin(x)
# 顺序 colormap
scatter1 = axes[0, 0].scatter(x, np.cos(x), c=range(len(x)), cmap='viridis', s=30)
axes[0, 0].set_title('viridis (顺序)')
fig.colorbar(scatter1, ax=axes[0, 0])
# 发散 colormap
scatter2 = axes[0, 1].scatter(x, np.cos(x), c=range(len(x)), cmap='RdBu_r', s=30)
axes[0, 1].set_title('RdBu_r (发散)')
fig.colorbar(scatter2, ax=axes[0, 1])
# 循环 colormap
for i in range(5):
axes[1, 0].plot(x, np.sin(x + i), color=plt.cm.Set2(i / 5),
linewidth=2, label=f'系列{i}')
axes[1, 0].set_title('Set2 (循环)')
axes[1, 0].legend()
# 自定义 colormap
from matplotlib.colors import LinearSegmentedColormap
colors_list = ['#2196F3', '#4CAF50', '#FF9800', '#F44336']
custom_cmap = LinearSegmentedColormap.from_list('custom', colors_list)
scatter3 = axes[1, 1].scatter(x, np.cos(x), c=range(len(x)), cmap=custom_cmap, s=30)
axes[1, 1].set_title('自定义 colormap')
fig.colorbar(scatter3, ax=axes[1, 1])
plt.tight_layout()
plt.savefig('colormaps.png', dpi=150)
plt.close()动画图表
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 简单动画:动态折线图
fig, ax = plt.subplots(figsize=(10, 6))
x_data = []
y_data = []
line, = ax.plot([], [], 'b-', linewidth=2)
ax.set_xlim(0, 100)
ax.set_ylim(0, 100)
ax.set_title('实时数据动画', fontsize=14)
ax.grid(True, alpha=0.3)
def init():
line.set_data([], [])
return line,
def animate(frame):
x_data.append(frame)
y_data.append(np.sin(frame * 0.1) * 30 + 50 + np.random.randn() * 5)
line.set_data(x_data, y_data)
# 动态调整 X 轴范围
if frame > 80:
ax.set_xlim(frame - 80, frame + 20)
return line,
anim = animation.FuncAnimation(
fig, animate, init_func=init,
frames=200, interval=50, blit=True
)
# 保存为 GIF(需要 pillow)
# anim.save('animation.gif', writer='pillow', fps=20)
# 保存为 MP4(需要 ffmpeg)
# anim.save('animation.mp4', writer='ffmpeg', fps=20)
plt.close()生产环境最佳实践
import matplotlib.pyplot as plt
import matplotlib as mpl
import numpy as np
# 1. 非交互式后端(服务器/CI 环境必须)
# 在导入 pyplot 之前设置
# mpl.use('Agg')
# 2. 封装通用图表生成器
class ChartGenerator:
"""生产环境图表生成器
特性:
- 统一样式配置
- 自动保存
- 支持返回 Figure 对象供测试
"""
# 统一配色方案
PRIMARY_COLORS = ['#4472C4', '#ED7D31', '#A5A5A5', '#FFC000', '#5B9BD5']
SUCCESS_COLOR = '#70AD47'
DANGER_COLOR = '#FF4444'
WARNING_COLOR = '#FFC000'
def __init__(self, style: str = 'default', dpi: int = 150):
self.dpi = dpi
self.style = style
plt.style.use(style)
def line_chart(
self,
x, y_series: dict,
title: str = '',
xlabel: str = '',
ylabel: str = '',
figsize: tuple = (10, 6),
) -> plt.Figure:
"""折线图"""
fig, ax = plt.subplots(figsize=figsize)
for idx, (name, y) in enumerate(y_series.items()):
ax.plot(x, y, color=self.PRIMARY_COLORS[idx % len(self.PRIMARY_COLORS)],
linewidth=2, label=name, marker='o', markersize=4)
ax.set_title(title, fontsize=14)
ax.set_xlabel(xlabel, fontsize=12)
ax.set_ylabel(ylabel, fontsize=12)
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
return fig
def save(self, fig: plt.Figure, path: str):
"""保存图表"""
fig.savefig(path, dpi=self.dpi, bbox_inches='tight',
facecolor='white', edgecolor='none')
plt.close(fig)
# 使用示例
gen = ChartGenerator()
months = list(range(1, 7))
fig = gen.line_chart(
months,
{'销售额': [120, 150, 180, 200, 220, 250], '利润': [30, 40, 55, 65, 75, 90]},
title='月度趋势',
xlabel='月份',
ylabel='万元'
)
gen.save(fig, 'report_chart.png')优点
缺点
总结
Matplotlib 核心:plt.figure() 创建画布,plt.plot/bar/scatter/hist 绑定图表。子图用 plt.subplots()。中文字体设置 plt.rcParams['font.sans-serif']。数值标签用 ax.text()。保存用 plt.savefig()。更美观的替代方案:Seaborn(统计图表)、Plotly(交互图表)、Pyecharts(Web 图表)。
关键知识点
- 面向对象方式(fig, ax = plt.subplots())比 pyplot 状态机方式更适合封装和复用
- 中文字体需要在 rcParams 中设置 font.sans-serif 和 axes.unicode_minus
- 保存图片时使用 bbox_inches='tight' 避免标签被截断
- 服务器/CI 环境需要使用 Agg 后端(mpl.use('Agg')),在导入 pyplot 之前设置
- 双 Y 轴使用 ax.twinx() 创建共享 X 轴的第二个坐标轴
项目落地视角
- 统一项目的图表样式,创建自定义 .mplstyle 文件
- 封装通用的图表生成函数,避免在每个分析脚本中重复样式代码
- 在 CI/CD 中使用 Agg 后端自动生成报告图表
- 图表保存为 SVG 格式以保证在任意分辨率下清晰
常见误区
- 把临时脚本直接当生产代码使用。
- 忽略依赖版本、编码、路径和时区差异。
- 只会写 happy path,没有补超时、重试和资源释放。
- 把 notebook 或脚本风格直接带入长期维护项目。
- 忘记调用 plt.close() 导致内存泄漏(在循环中大量生成图表时尤其危险)
- 在服务器环境中忘记设置 Agg 后端导致报错
- 中文字体设置不正确导致方块乱码
进阶路线
- 学习 Seaborn 实现更美观的统计图表
- 掌握 Plotly 实现交互式 Web 图表
- 学习 Pyecharts 实现基于 ECharts 的中文友好图表
- 深入 Matplotlib 的 Artist 对象系统,实现完全自定义的可视化
适用场景
- 当你准备把《Matplotlib 数据可视化》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合脚本自动化、数据处理、Web 开发和测试工具建设。
- 当需求强调快速迭代和丰富生态时,Python 往往能快速起步。
- 自动化报表生成、数据分析报告、科学论文插图、监控仪表盘等场景。
落地建议
- 统一使用虚拟环境与依赖锁定,避免环境漂移。
- 对核心函数补类型注解、异常处理和日志,减少"脚本黑盒"。
- 一旦脚本进入生产链路,及时补测试和监控。
- 创建统一的图表样式文件,确保项目输出风格一致。
排错清单
- 先确认当前解释器、虚拟环境和依赖版本是否正确。
- 检查编码、路径、时区和第三方库行为差异。
- 排查同步阻塞、数据库连接未释放或网络请求无超时。
- 检查中文字体是否安装并正确配置
- 确认是否在循环中遗漏了 plt.close()
复盘问题
- 如果把《Matplotlib 数据可视化》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《Matplotlib 数据可视化》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《Matplotlib 数据可视化》最大的收益和代价分别是什么?
