本节从审稿维度出发,从下面四个任务入手对图面精细标注。
为保证所有程序可直接运行,先运行以下代码生成所需的示例数据。
import numpy as np
import pandas as pd
np.random.seed(2024)
# ---------- 10.1 化学分子式X轴数据 ----------
molecules = ['H₂O', 'CO₂', 'CH₄', 'NH₃', 'C₂H₅OH', 'C₆H₁₂O₆']
values = [18.0, 44.0, 16.0, 17.0, 46.0, 180.0]
df_molecules = pd.DataFrame({'分子': molecules, '分子量': values})
df_molecules.to_csv('分子量数据.csv', index=False, encoding='utf-8-sig')
# ---------- 10.2 双Y轴数据(温度与湿度)----------
time = np.linspace(0, 24, 100) # 小时
temp = 15 + 8 * np.sin(2*np.pi*(time-6)/24) + np.random.normal(0, 0.5, 100)
humidity = 60 - 15 * np.sin(2*np.pi*(time-6)/24) + 5 * np.random.normal(0, 1, 100)
df_dual = pd.DataFrame({'时间': time, '温度': temp, '湿度': humidity})
df_dual.to_csv('温湿度数据.csv', index=False, encoding='utf-8-sig')
# ---------- 10.3 带标准差的图例数据 ----------
x = np.linspace(0, 10, 50)
y1 = 2*x + 5 + np.random.normal(0, 2, 50)
y2 = 1.5*x + 8 + np.random.normal(0, 3, 50)
df_std = pd.DataFrame({'x': x, '实验组': y1, '对照组': y2})
df_std.to_csv('实验数据_含标准差.csv', index=False, encoding='utf-8-sig')
# ---------- 10.4 含异常点的数据 ----------
x_anom = np.linspace(0, 20, 40)
y_anom = 0.5 * x_anom + 2 + np.random.normal(0, 1.5, 40)
# 人为插入两个异常点
y_anom[10] = 25# 异常高值
y_anom[25] = -3# 异常低值
df_anom = pd.DataFrame({'时间': x_anom, '信号': y_anom})
df_anom.to_csv('异常点数据.csv', index=False, encoding='utf-8-sig')
print("数据生成完成:")
print("- 分子量数据.csv(10.1节用)")
print("- 温湿度数据.csv(10.2节用)")
print("- 实验数据_含标准差.csv(10.3节用)")
print("- 异常点数据.csv(10.4节用)")
本任务从基础的文本刻度替换入手,逐步进阶到LaTeX渲染、自定义字体、旋转对齐及多行刻度。
直接用分子式字符串替换X轴刻度,matplotlib默认支持Unicode下标(如'₂')。
import pandas as pd
import matplotlib.pyplot as plt
# 学术样式设置
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('分子量数据.csv')
molecules = df['分子'].values
weights = df['分子量'].values
fig, ax = plt.subplots(figsize=(4.5, 3.5))
bars = ax.bar(range(len(molecules)), weights, color='#4472C4', alpha=0.7, edgecolor='black', linewidth=0.5)
# 设置X轴刻度和标签
ax.set_xticks(range(len(molecules)))
ax.set_xticklabels(molecules, fontsize=9)
ax.set_xlabel('分子式')
ax.set_ylabel('分子量 (g/mol)')
ax.set_ylim(0, max(weights)*1.1)
# 添加数值标注
for bar, w in zip(bars, weights):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
f'{w:.1f}', ha='center', va='bottom', fontsize=8)
plt.tight_layout()
plt.show()
fig.savefig('分子式刻度_基础.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('分子式刻度_基础.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

通过设置plt.rcParams['mathtext.fontset'] = 'cm',使用 LaTeX 的 Computer Modern 字体,获得漂亮的化
import pandas as pd
import matplotlib.pyplot as plt
defset_academic_style():
plt.rcParams['text.usetex'] = False
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['mathtext.fontset'] = 'cm'# latex 字体 Computer Modern
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('分子量数据.csv')
# LaTeX格式分子式
latex_molecules = [r'H$_2$O', r'CO$_2$', r'CH$_4$', r'NH$_3$', r'C$_2$H$_5$OH', r'C$_6$H$_{12}$O$_6$']
weights = df['分子量'].values
fig, ax = plt.subplots(figsize=(4.5, 3.5))
bars = ax.bar(range(len(latex_molecules)), weights, color='#4472C4', alpha=0.7, edgecolor='black', linewidth=0.5)
ax.set_xticks(range(len(latex_molecules)))
ax.set_xticklabels(latex_molecules, fontsize=10)
ax.set_xlabel('分子式')
ax.set_ylabel('分子量 (g/mol)')
ax.set_ylim(0, max(weights)*1.1)
for bar, w in zip(bars, weights):
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
f'{w:.1f}', ha='center', va='bottom', fontsize=8)
plt.tight_layout()
plt.show()
fig.savefig('分子式刻度2_LaTeX.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('分子式刻度2_LaTeX.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

$...$数学模式自动实现,变量字母为斜体,数字和下标为正体。对特定分子式设置不同字体属性,如将CO₂设为红色粗体以强调。
import pandas as pd
import matplotlib.pyplot as plt
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['mathtext.fontset'] = 'cm'
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('分子量数据.csv')
math_molecules = [r'$H_2O$', r'$CO_2$', r'$CH_4$', r'$NH_3$', r'$C_2H_5OH$', r'$C_6H_{12}O_6$']
weights = df['分子量'].values
fig, ax = plt.subplots(figsize=(5.0, 3.5))
bars = ax.bar(range(len(math_molecules)), weights, color='#4472C4', alpha=0.7, edgecolor='black', linewidth=0.5)
ax.set_xticks(range(len(math_molecules)))
ax.set_xticklabels(math_molecules, fontsize=9)
# 单独设置CO₂为红色粗体
xticklabels = ax.get_xticklabels()
for i, label in enumerate(xticklabels):
if'CO'in label.get_text():
label.set_color('red')
label.set_fontweight('bold')
label.set_fontsize(10)
ax.set_xlabel('分子式')
ax.set_ylabel('分子量 (g/mol)')
plt.tight_layout()
plt.show()
fig.savefig('分子式刻度_强调特定标签.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('分子式刻度_强调特定标签.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

xticklabels列表,可对单个标签设置颜色、粗细、大小等属性。当分子式名称过长时,可换行显示并适当旋转,避免重叠。
import pandas as pd
import matplotlib.pyplot as plt
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['mathtext.fontset'] = 'cm'
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 300
set_academic_style()
df = pd.read_csv('分子量数据.csv')
# 添加更长的名称演示多行
molecules_long = ['H₂O', 'CO₂', 'CH₄', 'NH₃', '乙醇\nC₂H₅OH', '葡萄糖\nC₆H₁₂O₆']
weights = df['分子量'].values
fig, ax = plt.subplots(figsize=(5.0, 4.0))
bars = ax.bar(range(len(molecules_long)), weights, color='#4472C4', alpha=0.7, edgecolor='black', linewidth=0.5)
ax.set_xticks(range(len(molecules_long)))
ax.set_xticklabels(molecules_long, fontsize=8, ha='center')
ax.set_xlabel('分子式')
ax.set_ylabel('分子量 (g/mol)')
ax.set_xlim(-0.5, len(molecules_long)-0.5)
# 调整底部边距以容纳多行标签
plt.subplots_adjust(bottom=0.15)
plt.show()
fig.savefig('分子式刻度_多行标签.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('分子式刻度_多行标签.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

\n实现换行,第一行为常用名,第二行为分子式。subplots_adjust)防止标签被截断。ax.set_xticklabels(..., rotation=30, ha='right')。本任务从基础双轴图入手,逐步进阶到颜色同步、图例整合、多曲线及共享X轴子图。
使用twinx()创建共享X轴的第二Y轴。
import pandas as pd
import matplotlib.pyplot as plt
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('温湿度数据.csv')
time = df['时间']
temp = df['温度']
hum = df['湿度']
fig, ax1 = plt.subplots(figsize=(5.0, 3.5))
# 左轴:温度
ax1.plot(time, temp, 'r-', linewidth=1.5, label='温度')
ax1.set_xlabel('时间 (h)')
ax1.set_ylabel('温度 (°C)', color='red')
ax1.tick_params(axis='y', labelcolor='red')
# 右轴:湿度
ax2 = ax1.twinx()
ax2.plot(time, hum, 'b--', linewidth=1.5, label='湿度')
ax2.set_ylabel('相对湿度 (%)', color='blue')
ax2.tick_params(axis='y', labelcolor='blue')
plt.tight_layout()
plt.show()
fig.savefig('双Y轴_基础.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('双Y轴_基础.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

将两条曲线的图例合并显示,避免重复。
import pandas as pd
import matplotlib.pyplot as plt
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('温湿度数据.csv')
time, temp, hum = df['时间'], df['温度'], df['湿度']
fig, ax1 = plt.subplots(figsize=(5.0, 3.5))
ax2 = ax1.twinx()
line1, = ax1.plot(time, temp, color='#C00000', linewidth=1.8, label='温度')
line2, = ax2.plot(time, hum, color='#0066CC', linewidth=1.8, linestyle='--', label='相对湿度')
ax1.set_xlabel('时间 (h)')
ax1.set_ylabel('温度 (°C)', color='#C00000')
ax2.set_ylabel('相对湿度 (%)', color='#0066CC')
ax1.tick_params(axis='y', labelcolor='#C00000')
ax2.tick_params(axis='y', labelcolor='#0066CC')
# 合并图例
lines = [line1, line2]
labels = [l.get_label() for l in lines]
ax1.legend(lines, labels, loc='upper right', fontsize=8, frameon=False)
plt.tight_layout()
plt.show()
fig.savefig('双Y轴_统一图例.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('双Y轴_统一图例.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

通过labelpad和set_position调整轴标签与轴的距离,避免与刻度数字重叠。
import pandas as pd
import matplotlib.pyplot as plt
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('温湿度数据.csv')
time, temp, hum = df['时间'], df['温度'], df['湿度']
fig, ax1 = plt.subplots(figsize=(5.0, 3.5))
ax2 = ax1.twinx()
ax1.plot(time, temp, color='#C00000', linewidth=1.8, label='温度')
ax2.plot(time, hum, color='#0066CC', linewidth=1.8, linestyle='--', label='湿度')
ax1.set_xlabel('时间 (h)')
ax1.set_ylabel('温度 (°C)', color='#C00000', labelpad=10) # 增加标签与轴的距离
ax2.set_ylabel('相对湿度 (%)', color='#0066CC', labelpad=15)
# 右轴标签旋转(可选)
ax2.yaxis.set_label_coords(1.08, 0.5) # 调整标签水平位置
ax1.tick_params(axis='y', labelcolor='#C00000')
ax2.tick_params(axis='y', labelcolor='#0066CC')
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1+lines2, labels1+labels2, loc='upper right', fontsize=8)
plt.tight_layout()
plt.show()
fig.savefig('双Y轴_标签位置调整.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('双Y轴_标签位置调整.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

labelpad控制标签与轴线的距离,避免与刻度数字重叠。set_label_coords可精细调整标签位置,适用于标签较长或需要特殊对齐时。左轴绘制多条温度曲线(如不同实验组),右轴仍为湿度。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('温湿度数据.csv')
time = df['时间']
temp1 = df['温度'] # 实验组1
# 模拟实验组2和3
np.random.seed(42)
temp2 = temp1 + 2 + np.random.normal(0, 0.8, len(time))
temp3 = temp1 - 1.5 + np.random.normal(0, 0.6, len(time))
hum = df['湿度']
fig, ax1 = plt.subplots(figsize=(5.5, 3.8))
ax2 = ax1.twinx()
# 左轴:三条温度曲线
l1, = ax1.plot(time, temp1, color='#C00000', linewidth=1.5, label='实验组1')
l2, = ax1.plot(time, temp2, color='#ED7D31', linewidth=1.5, linestyle='-.', label='实验组2')
l3, = ax1.plot(time, temp3, color='#70AD47', linewidth=1.5, linestyle=':', label='实验组3')
# 右轴:湿度
l4, = ax2.plot(time, hum, color='#0066CC', linewidth=1.8, linestyle='--', label='相对湿度')
ax1.set_xlabel('时间 (h)')
ax1.set_ylabel('温度 (°C)')
ax2.set_ylabel('相对湿度 (%)', color='#0066CC')
ax1.tick_params(axis='y')
ax2.tick_params(axis='y', labelcolor='#0066CC')
# 图例整合
lines = [l1, l2, l3, l4]
labels = [l.get_label() for l in lines]
ax1.legend(lines, labels, loc='upper left', fontsize=7, frameon=False, ncol=2)
plt.tight_layout()
plt.show()
fig.savefig('双Y轴_多曲线.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('双Y轴_多曲线.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

使用subplots创建上下两个子图,各自拥有双Y轴,共享X轴。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('温湿度数据.csv')
time = df['时间']
temp = df['温度']
hum = df['湿度']
# 模拟另一组数据(风速)
wind = 2 + 1.5 * np.sin(2*np.pi*(time-3)/12) + np.random.normal(0, 0.3, len(time))
fig, (ax1, ax3) = plt.subplots(2, 1, figsize=(5.0, 5.0), sharex=True)
# 上图:温度(左)+ 湿度(右)
ax2 = ax1.twinx()
ax1.plot(time, temp, 'r-', label='温度')
ax2.plot(time, hum, 'b--', label='湿度')
ax1.set_ylabel('温度 (°C)', color='red')
ax2.set_ylabel('湿度 (%)', color='blue')
ax1.tick_params(axis='y', labelcolor='red')
ax2.tick_params(axis='y', labelcolor='blue')
# 下图:温度(左)+ 风速(右)
ax4 = ax3.twinx()
ax3.plot(time, temp, 'r-', label='温度')
ax4.plot(time, wind, 'g-.', label='风速')
ax3.set_xlabel('时间 (h)')
ax3.set_ylabel('温度 (°C)', color='red')
ax4.set_ylabel('风速 (m/s)', color='green')
ax3.tick_params(axis='y', labelcolor='red')
ax4.tick_params(axis='y', labelcolor='green')
# 图例
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1+lines2, labels1+labels2, loc='upper right', fontsize=7)
lines3, labels3 = ax3.get_legend_handles_labels()
lines4, labels4 = ax4.get_legend_handles_labels()
ax3.legend(lines3+lines4, labels3+labels4, loc='upper right', fontsize=7)
plt.tight_layout()
plt.show()
fig.savefig('双Y轴_共享X子图.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('双Y轴_共享X子图.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

本任务从手动创建图例句柄入手,逐步进阶到自定义色块、标准差阴影图例、多色块及图例位置精细调整。
使用Patch创建自定义图例元素,表示数据标准差。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('实验数据_含标准差.csv')
x = df['x']
y_exp = df['实验组']
y_ctrl = df['对照组']
# 计算总体标准差(示例)
std_exp = np.std(y_exp)
std_ctrl = np.std(y_ctrl)
fig, ax = plt.subplots(figsize=(5.0, 3.5))
line1, = ax.plot(x, y_exp, 'o-', color='#C00000', markersize=4, label='实验组')
line2, = ax.plot(x, y_ctrl, 's-', color='#0066CC', markersize=4, label='对照组')
# 创建自定义图例句柄:矩形色块表示标准差
std_patch1 = mpatches.Patch(color='#C00000', alpha=0.3, label=f'实验组 SD = {std_exp:.2f}')
std_patch2 = mpatches.Patch(color='#0066CC', alpha=0.3, label=f'对照组 SD = {std_ctrl:.2f}')
# 合并默认图例句柄与自定义
handles, labels = ax.get_legend_handles_labels()
handles.extend([std_patch1, std_patch2])
labels.extend([std_patch1.get_label(), std_patch2.get_label()])
ax.legend(handles=handles, labels=labels, loc='upper left', fontsize=8, frameon=False)
ax.set_xlabel('x')
ax.set_ylabel('测量值')
ax.set_title('实验数据(图例含标准差)', fontsize=10)
plt.tight_layout()
plt.show()
fig.savefig('图例_标准差色块_基础.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('图例_标准差色块_基础.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

mpatches.Patch创建半透明矩形,颜色与对应曲线一致,alpha表示“范围”概念。先绘制带标准差的阴影区域,再在图例中用相同样式的色块表示。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('实验数据_含标准差.csv')
x = df['x']
y_exp = df['实验组']
y_ctrl = df['对照组']
# 计算均值和标准差(按x分组计算更合理,此处简化用总体标准差展示方法)
# 实际应用中应对每个x点计算重复测量的std,此处仅演示图例
mean_exp = np.mean(y_exp)
mean_ctrl = np.mean(y_ctrl)
std_exp = np.std(y_exp)
std_ctrl = np.std(y_ctrl)
fig, ax = plt.subplots(figsize=(5.0, 3.5))
# 绘制带标准差的阴影区域(使用总体均值±std示意)
x_fill = np.array([x.min(), x.max(), x.max(), x.min()])
y_fill_exp = np.array([mean_exp-std_exp, mean_exp-std_exp, mean_exp+std_exp, mean_exp+std_exp])
y_fill_ctrl = np.array([mean_ctrl-std_ctrl, mean_ctrl-std_ctrl, mean_ctrl+std_ctrl, mean_ctrl+std_ctrl])
ax.fill(x_fill, y_fill_exp, color='#C00000', alpha=0.2, label='_nolegend_')
ax.fill(x_fill, y_fill_ctrl, color='#0066CC', alpha=0.2, label='_nolegend_')
line1, = ax.plot(x, y_exp, 'o-', color='#C00000', markersize=4, label='实验组')
line2, = ax.plot(x, y_ctrl, 's-', color='#0066CC', markersize=4, label='对照组')
# 自定义图例
std_patch1 = mpatches.Patch(color='#C00000', alpha=0.2, label=f'实验组 SD = {std_exp:.2f}')
std_patch2 = mpatches.Patch(color='#0066CC', alpha=0.2, label=f'对照组 SD = {std_ctrl:.2f}')
handles = [line1, std_patch1, line2, std_patch2]
ax.legend(handles=handles, loc='upper left', fontsize=8, frameon=False)
ax.set_xlabel('x')
ax.set_ylabel('测量值')
plt.tight_layout()
plt.show()
fig.savefig('图例_标准差阴影同步.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('图例_标准差阴影同步.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

label='_nolegend_'防止fill对象自动进入图例,保持图例简洁。用不同透明度的色块表示68%、95%置信区间,并在图例中分别标注。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['mathtext.fontset'] = 'cm'
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('实验数据_含标准差.csv')
x = df['x']
y = df['实验组']
mean_y = np.mean(y)
std_y = np.std(y)
fig, ax = plt.subplots(figsize=(5.0, 3.5))
# 绘制68% (±1σ) 和 95% (±2σ) 区间
x_fill = np.array([x.min(), x.max(), x.max(), x.min()])
y_fill_1 = [mean_y-std_y, mean_y-std_y, mean_y+std_y, mean_y+std_y]
y_fill_2 = [mean_y-2*std_y, mean_y-2*std_y, mean_y+2*std_y, mean_y+2*std_y]
ax.fill(x_fill, y_fill_2, color='#4472C4', alpha=0.15, label='_nolegend_')
ax.fill(x_fill, y_fill_1, color='#4472C4', alpha=0.3, label='_nolegend_')
line, = ax.plot(x, y, 'o', color='#4472C4', markersize=4, alpha=0.6, label='数据点')
ax.axhline(mean_y, color='#4472C4', linestyle='--', linewidth=1, label='均值')
# 图例
patch_95 = mpatches.Patch(color='#4472C4', alpha=0.15, label='95% CI ($\pm 2 \sigma$)')
patch_68 = mpatches.Patch(color='#4472C4', alpha=0.3, label='68% CI ($\pm 1 \sigma$)')
handles = [line, ax.get_lines()[0], patch_68, patch_95] # 均值线是第二个Line2D对象
labels = ['数据点', f'均值 = {mean_y:.2f}', '68% CI ($\pm 1 \sigma$)', '95% CI ($\pm 2 \sigma$)']
ax.legend(handles=handles, labels=labels, loc='lower right', fontsize=8, frameon=False)
ax.set_xlabel('x')
ax.set_ylabel('测量值')
ax.set_title('置信区间可视化', fontsize=10)
plt.tight_layout()
plt.show()
fig.savefig('图例_多级标准差色块.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('图例_多级标准差色块.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

将图例分为多列,添加标题,并设置半透明背景以融入图表。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('实验数据_含标准差.csv')
x, y_exp, y_ctrl = df['x'], df['实验组'], df['对照组']
fig, ax = plt.subplots(figsize=(5.5, 3.8))
line1, = ax.plot(x, y_exp, 'o-', color='#C00000', markersize=4, label='实验组')
line2, = ax.plot(x, y_ctrl, 's-', color='#0066CC', markersize=4, label='对照组')
std_patch1 = mpatches.Patch(color='#C00000', alpha=0.3, label=f'实验组 SD = {np.std(y_exp):.2f}')
std_patch2 = mpatches.Patch(color='#0066CC', alpha=0.3, label=f'对照组 SD = {np.std(y_ctrl):.2f}')
# 图例:分两列,带标题,半透明背景
legend = ax.legend(handles=[line1, std_patch1, line2, std_patch2],
loc='upper center', bbox_to_anchor=(0.5, -0.15),
ncol=2, fontsize=8, frameon=True, fancybox=False,
edgecolor='black', facecolor='white', framealpha=0.8)
legend.set_title('图例说明', prop={'size': 9, 'weight': 'bold'})
ax.set_xlabel('x')
ax.set_ylabel('测量值')
plt.subplots_adjust(bottom=0.25) # 为图例留空间
plt.show()
fig.savefig('图例_布局优化.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('图例_布局优化.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

frameon=True)并设置fancybox=False获得直角矩形,framealpha=0.8使背景半透。HandlerTuple组合图例(线+色块)将曲线和对应的标准差色块组合为单个图例条目,更紧凑。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerTuple
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('实验数据_含标准差.csv')
x, y_exp, y_ctrl = df['x'], df['实验组'], df['对照组']
fig, ax = plt.subplots(figsize=(5.0, 3.5))
line1, = ax.plot(x, y_exp, 'o-', color='#C00000', markersize=4)
line2, = ax.plot(x, y_ctrl, 's-', color='#0066CC', markersize=4)
# 创建组合图例句柄:每个组由(线, 色块)组成
from matplotlib.patches import Patch
std_patch1 = Patch(color='#C00000', alpha=0.3)
std_patch2 = Patch(color='#0066CC', alpha=0.3)
ax.legend([(line1, std_patch1), (line2, std_patch2)],
[f'实验组 (SD={np.std(y_exp):.2f})', f'对照组 (SD={np.std(y_ctrl):.2f})'],
handler_map={tuple: HandlerTuple(ndivide=None)},
loc='upper left', fontsize=8, frameon=False)
ax.set_xlabel('x')
ax.set_ylabel('测量值')
plt.tight_layout()
plt.show()
fig.savefig('图例_组合句柄.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('图例_组合句柄.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

HandlerTuple将线条和色块组合为一个图例条目,图例更加紧凑。本任务从基础的annotate文本标注入手,逐步进阶到箭头样式自定义、多异常点标注、框体美化及自动定位。
用annotate在异常点旁添加文本框和箭头。
import pandas as pd
import matplotlib.pyplot as plt
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('异常点数据.csv')
x = df['时间']
y = df['信号']
fig, ax = plt.subplots(figsize=(5.0, 3.5))
ax.plot(x, y, 'o-', color='#4472C4', markersize=4, linewidth=1)
# 标注异常高值点(索引10,x≈5,y≈25)
idx_high = 10
ax.annotate('异常高值\n(仪器饱和)',
xy=(x[idx_high], y[idx_high]),
xytext=(x[idx_high]+3, y[idx_high]-5),
arrowprops=dict(arrowstyle='->', color='red', lw=1.5),
fontsize=8, color='red', ha='center',
bbox=dict(boxstyle='round,pad=0.3', facecolor='white', edgecolor='red', alpha=0.8))
ax.set_xlabel('时间')
ax.set_ylabel('信号强度')
ax.set_title('异常点标注', fontsize=10)
plt.tight_layout()
plt.show()
fig.savefig('异常点标注_基础.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('异常点标注_基础.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

xy指定箭头指向的坐标,xytext指定文本框位置。修改arrowprops字典,实现更丰富的箭头外观。
import pandas as pd
import matplotlib.pyplot as plt
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('异常点数据.csv')
x, y = df['时间'], df['信号']
fig, ax = plt.subplots(figsize=(5.0, 3.5))
ax.plot(x, y, 'o-', color='#4472C4', markersize=4, linewidth=1)
idx_low = 25
ax.annotate('异常低值\n(传感器故障)',
xy=(x[idx_low], y[idx_low]),
xytext=(x[idx_low]-4, y[idx_low]+10),
arrowprops=dict(arrowstyle='fancy', connectionstyle='arc3,rad=0.3',
color='red', lw=1.5, linestyle='--'),
fontsize=8, color='red', ha='center',
bbox=dict(boxstyle='round,pad=0.3', facecolor='lightyellow', edgecolor='blue', alpha=0.9))
ax.set_xlabel('时间')
ax.set_ylabel('信号强度')
plt.tight_layout()
plt.show()
fig.savefig('异常点标注_自定义箭头.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('异常点标注_自定义箭头.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

arrowstyle='fancy'生成带弧度的箭头,connectionstyle='arc3,rad=0.3'控制弯曲程度。linestyle='--')使箭头更柔和。在图上标注多个异常点,并为每个异常点添加编号,同时在图例中说明。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('异常点数据.csv')
x, y = df['时间'], df['信号']
fig, ax = plt.subplots(figsize=(5.5, 4.0))
ax.plot(x, y, 'o-', color='#4472C4', markersize=4, linewidth=1, label='正常信号')
# 定义异常点及其说明
anomalies = [(10, '高值饱和'), (25, '低值故障'), (35, '尖峰干扰')]
colors = ['red', 'blue', 'green']
for i, (idx, desc) in enumerate(anomalies):
# 标记异常点(大星号)
ax.plot(x[idx], y[idx], marker='*', color=colors[i], markersize=12,
markeredgecolor='black', markeredgewidth=0.5, label=f'异常{i+1}: {desc}')
# 添加文本框
ax.annotate(f'异常{i+1}: {desc}',
xy=(x[idx], y[idx]),
xytext=(x[idx]+(-1if i%2==0else2), y[idx]+(-3if i==1else5)),
arrowprops=dict(arrowstyle='->', color=colors[i], lw=1.2),
fontsize=7, color=colors[i],
bbox=dict(boxstyle='round,pad=0.2', facecolor='white', edgecolor=colors[i], alpha=0.7))
ax.set_xlabel('时间')
ax.set_ylabel('信号强度')
ax.legend(loc='upper right', fontsize=7, ncol=2, frameon=True, fancybox=False, edgecolor='gray')
plt.tight_layout()
plt.show()
fig.savefig('异常点标注_多异常点.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('异常点标注_多异常点.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

为文本框添加阴影和更精致的边框样式。
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import FancyBboxPatch
import matplotlib.patheffects as path_effects
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('异常点数据.csv')
x, y = df['时间'], df['信号']
fig, ax = plt.subplots(figsize=(5.0, 3.5))
ax.plot(x, y, 'o-', color='#4472C4', markersize=4, linewidth=1)
idx_high = 10
# 使用带阴影效果的文本框
text = ax.annotate('异常高值\n(仪器饱和)',
xy=(x[idx_high], y[idx_high]),
xytext=(x[idx_high]+3, y[idx_high]-5),
arrowprops=dict(arrowstyle='wedge,tail_width=0.5',
color='darkred', lw=1.5, shrinkA=5, shrinkB=5),
fontsize=9, color='darkred', ha='center', weight='bold',
bbox=dict(boxstyle='round,pad=0.5', facecolor='#FFF0F0',
edgecolor='darkred', linewidth=1.5, alpha=0.95))
# 添加文字阴影效果(需在渲染后获取text对象)
# text.set_path_effects([path_effects.withSimplePatchShadow(offset=(2,-2), shadow_rgbFace='gray', alpha=0.5)])
ax.set_xlabel('时间')
ax.set_ylabel('信号强度')
plt.tight_layout()
plt.show()
fig.savefig('异常点标注_美化框体.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('异常点标注_美化框体.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

arrowstyle='wedge,tail_width=0.5'产生楔形箭头,更醒目。path_effects(注释部分),但需注意兼容性。当异常点较多时,手动指定xytext易导致重叠。可通过简单的偏移策略或第三方库adjustText自动优化。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from adjustText import adjust_text # 需安装:pip install adjustText
defset_academic_style():
plt.rcParams['font.family'] = ['Times New Roman', 'SimSun']
plt.rcParams['font.size'] = 9
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['axes.linewidth'] = 1.0
plt.rcParams['xtick.major.width'] = 1.0
plt.rcParams['ytick.major.width'] = 1.0
plt.rcParams['xtick.major.size'] = 3.5
plt.rcParams['ytick.major.size'] = 3.5
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['legend.frameon'] = False
plt.rcParams['pdf.fonttype'] = 42
plt.rcParams['savefig.dpi'] = 1200
set_academic_style()
df = pd.read_csv('异常点数据.csv')
x, y = df['时间'], df['信号']
# 模拟更多异常点
np.random.seed(42)
anom_indices = [10, 15, 20, 25, 30, 35]
descs = ['饱和', '尖峰', '漂移', '故障', '噪声', '干扰']
fig, ax = plt.subplots(figsize=(6.0, 4.0))
ax.plot(x, y, 'o-', color='#4472C4', markersize=4, linewidth=1)
texts = []
for idx, desc in zip(anom_indices, descs):
# 初始位置在点右上方随机偏移
xytext = (x[idx] + np.random.uniform(1, 3), y[idx] + np.random.uniform(2, 5))
txt = ax.annotate(desc, xy=(x[idx], y[idx]), xytext=xytext,
arrowprops=dict(arrowstyle='->', color='gray', lw=1),
fontsize=7, bbox=dict(boxstyle='round,pad=0.2', facecolor='white', alpha=0.8))
texts.append(txt)
# 自动调整文本位置避免重叠
adjust_text(texts, x=x[anom_indices], y=y[anom_indices], arrowprops=dict(arrowstyle='->', color='gray', lw=1),
expand_text=(1.2, 1.5), expand_points=(1.5, 1.8))
ax.set_xlabel('时间')
ax.set_ylabel('信号强度')
ax.set_title('自动避让的异常点标注', fontsize=10)
plt.tight_layout()
plt.show()
fig.savefig('异常点标注_自动定位.pdf', bbox_inches='tight', pad_inches=0.05)
fig.savefig('异常点标注_自动定位.png', bbox_inches='tight', pad_inches=0.05)
执行结果分析:

adjust_text库迭代调整文本框位置,最小化重叠,同时保持箭头指向原始点。