078、matplotlib 绘图实战Figure/Axes 模型、样式定制、中文字体解决上周帮同事调试一个数据可视化脚本他对着屏幕抓狂了半小时——明明代码逻辑没问题生成的折线图却全是方框乱码标题和坐标轴标签像被外星人劫持了一样。我凑过去一看又是中文字体没配置的老问题。更离谱的是他试图用plt.rcParams[font.sans-serif] [SimHei]解决结果在Mac上直接报错找不到字体。这种坑我踩过不下十次今天干脆把Figure/Axes模型、样式定制和中文字体方案一次性讲透。Figure与Axes别再把它们当一回事很多初学者把matplotlib当成一个黑盒上来就plt.plot()完事。但真正要做出可复用的专业图表必须理解Figure和Axes的关系。Figure是整个画布你可以把它想象成一块画板Axes是画板上的子图区域每个Axes有自己的坐标系。一个Figure可以包含多个Axes就像一张纸上画多个小图。看这个典型错误有人想画两个子图直接写两遍plt.plot()结果发现第二个图覆盖了第一个。这是因为plt.plot()默认操作的是最后一个激活的Axes。正确做法是显式创建Figure和Axesimportmatplotlib.pyplotaspltimportnumpyasnp# 创建画布和子图——这里指定了1行2列返回figure对象和axes数组fig,axesplt.subplots(1,2,figsize(10,4))# 生成数据xnp.linspace(0,10,100)y1np.sin(x)y2np.cos(x)# 在第一个Axes上画图——别写成axes[0].plot()时忘了指定数据维度axes[0].plot(x,y1,color#FF6B6B,linewidth2,labelsin(x))axes[0].set_title(正弦曲线,fontsize12)axes[0].legend()# 第二个Axesaxes[1].plot(x,y2,color#4ECDC4,linewidth2,linestyle--,labelcos(x))axes[1].set_title(余弦曲线,fontsize12)axes[1].legend()# 调整布局防止重叠——这里踩过坑不写tight_layout的话标题会被切掉plt.tight_layout()plt.show()注意subplots返回的是元组解包时别漏了。如果只想要单个Axes可以写fig, ax plt.subplots()这样ax就是一个Axes对象而不是数组。样式定制从默认丑图到专业级图表matplotlib默认样式是上世纪80年代的审美——灰色背景、锯齿线条、刺眼颜色。好在它提供了样式系统一行代码就能切换主题。我常用的几个# 查看所有可用样式print(plt.style.available)# 直接切换——推荐seaborn-v0_8或ggplotplt.style.use(seaborn-v0_8)但样式模板只是起点真正让图表专业的是细节定制。比如坐标轴刻度、网格线、图例位置这些默认值往往不够用。看这个例子我故意把参数写得很啰嗦方便你理解每个参数的作用fig,axplt.subplots(figsize(8,5))# 生成带噪声的数据np.random.seed(42)xnp.arange(0,10,0.5)y2*x1np.random.normal(0,1,len(x))# 散点图——marker参数用o表示圆形s控制大小ax.scatter(x,y,color#2C3E50,s50,alpha0.7,edgecolorswhite,linewidth0.5)# 拟合直线——别这样写直接用np.polyfit然后手动计算太麻烦# 这里用numpy的polyfit和poly1d一步到位coeffsnp.polyfit(x,y,1)polynp.poly1d(coeffs)ax.plot(x,poly(x),color#E74C3C,linewidth2,linestyle-,labelf拟合线: y{coeffs[0]:.2f}x{coeffs[1]:.2f})# 定制坐标轴——去掉上边框和右边框更清爽ax.spines[top].set_visible(False)ax.spines[right].set_visible(False)ax.spines[left].set_color(#7F8C8D)ax.spines[bottom].set_color(#7F8C8D)# 网格线——只显示y轴网格避免视觉杂乱ax.grid(axisy,linestyle--,alpha0.6,color#BDC3C7)ax.grid(axisx,linestyle,alpha0)# 显式关闭x轴网格# 图例——放在左上角去掉边框ax.legend(locupper left,frameonFalse,fontsize10)# 标题和标签——这里用LaTeX语法写数学符号ax.set_title(线性回归拟合结果,fontsize14,fontweightbold,pad15)ax.set_xlabel(自变量 X,fontsize11,labelpad8)ax.set_ylabel(因变量 Y,fontsize11,labelpad8)plt.tight_layout()plt.show()关于颜色我建议用专业配色方案比如从colorbrewer或material design色板里选。别用默认的蓝色橙色太刺眼。我常用#2C3E50深蓝灰、#E74C3C红、#3498DB蓝、#2ECC71绿这组视觉上比较平衡。中文字体绕不开的坑与终极方案中文字体问题是matplotlib最大的痛点之一。根源在于matplotlib默认字体不支持中文而不同操作系统预装的中文字体名不同。Windows有SimHei、Microsoft YaHeiMac有PingFang SC、STHeitiLinux更乱。直接写死字体名换个环境就崩。我试过几种方案最终稳定下来的是用font_manager动态查找系统字体importmatplotlib.font_managerasfm# 查找系统中所有支持中文的字体——这里踩过坑直接写字体名可能找不到deffind_chinese_font():自动查找系统中可用的中文字体返回字体属性对象# 优先尝试的字体列表按常用度排序preferred_fonts[PingFang SC,# MacMicrosoft YaHei,# WindowsSimHei,# Windows 备选STHeiti,# Mac 备选WenQuanYi Micro Hei,# LinuxNoto Sans CJK SC,# Linux 备选Source Han Sans SC# 思源黑体]# 遍历查找forfont_nameinpreferred_fonts:try:font_propfm.FontProperties(familyfont_name)# 验证字体是否可用——直接设置后检查比单纯查找更可靠test_fontfm.FontProperties(familyfont_name)iftest_font.get_name()!sans-serif:# 如果返回默认字体名说明没找到returnfont_propexcept:continue# 兜底方案让matplotlib自己找returnfm.FontProperties()# 使用示例font_propfind_chinese_font()fig,axplt.subplots(figsize(8,4))x[一月,二月,三月,四月,五月]y[23,45,56,78,89]ax.bar(x,y,color#3498DB,alpha0.8,edgecolorwhite)# 关键所有中文文本都要指定fontproperties参数——别偷懒只设置rcParamsax.set_title(月度销售数据,fontpropertiesfont_prop,fontsize14)ax.set_xlabel(月份,fontpropertiesfont_prop,fontsize11)ax.set_ylabel(销售额万元,fontpropertiesfont_prop,fontsize11)# 坐标轴刻度标签也要处理——这里用set_xticklabels重新设置ax.set_xticklabels(x,fontpropertiesfont_prop,fontsize10)plt.tight_layout()plt.show()如果你嫌每次都要传fontproperties太麻烦可以全局设置rcParams。但注意这个方案在有些环境下会失效比如Jupyter Notebook的某些内核# 全局设置——但别完全依赖它有些场景下不生效plt.rcParams[font.sans-serif][PingFang SC,Microsoft YaHei,SimHei]plt.rcParams[axes.unicode_minus]False# 解决负号显示为方块的问题我的建议是写一个工具函数set_chinese_font()在脚本开头调用一次然后所有文本都通过fontpropertiesfont_prop显式指定。这样即使换环境只要系统有中文字体就能自动适配。实战一个完整的仪表盘风格图表最后分享一个我实际项目中用过的模板结合了Figure/Axes布局、样式定制和中文支持。这个图表用于展示模型训练过程中的损失和准确率变化importmatplotlib.pyplotaspltimportnumpyasnpimportmatplotlib.font_managerasfm# 字体初始化font_propfm.FontProperties(familyPingFang SC)# 根据你的系统调整# 创建画布——2行1列共享x轴fig,(ax1,ax2)plt.subplots(2,1,figsize(10,6),sharexTrue)# 模拟训练数据epochsnp.arange(1,51)train_loss0.8*np.exp(-0.05*epochs)0.1*np.random.randn(50)val_loss0.9*np.exp(-0.04*epochs)0.15*np.random.randn(50)train_acc0.50.4*(1-np.exp(-0.08*epochs))0.02*np.random.randn(50)val_acc0.450.4*(1-np.exp(-0.06*epochs))0.03*np.random.randn(50)# 第一个子图损失曲线ax1.plot(epochs,train_loss,color#E74C3C,linewidth1.5,label训练损失,alpha0.8)ax1.plot(epochs,val_loss,color#3498DB,linewidth1.5,label验证损失,alpha0.8,linestyle--)ax1.set_ylabel(损失值,fontpropertiesfont_prop,fontsize11)ax1.set_title(模型训练过程监控,fontpropertiesfont_prop,fontsize14,fontweightbold)ax1.legend(propfont_prop,frameonFalse,locupper right)ax1.grid(axisy,linestyle--,alpha0.4)ax1.spines[top].set_visible(False)ax1.spines[right].set_visible(False)# 第二个子图准确率曲线ax2.plot(epochs,train_acc,color#2ECC71,linewidth1.5,label训练准确率,alpha0.8)ax2.plot(epochs,val_acc,color#F39C12,linewidth1.5,label验证准确率,alpha0.8,linestyle--)ax2.set_xlabel(训练轮次,fontpropertiesfont_prop,fontsize11)ax2.set_ylabel(准确率,fontpropertiesfont_prop,fontsize11)ax2.legend(propfont_prop,frameonFalse,loclower right)ax2.grid(axisy,linestyle--,alpha0.4)ax2.spines[top].set_visible(False)ax2.spines[right].set_visible(False)# 调整布局——这里用subplots_adjust手动控制间距比tight_layout更灵活plt.subplots_adjust(hspace0.25)# 保存为高清图片——dpi300用于论文或报告plt.savefig(training_monitor.png,dpi300,bbox_inchestight)plt.show()个人经验总结永远不要依赖默认样式。每次新建图表先想清楚受众是谁——给老板看用大字号、高对比度给论文用黑白兼容配色给团队内部用简洁风格。中文字体问题提前在开发环境配好。我习惯在项目根目录放一个fonts/文件夹里面存一份思源黑体Source Han Sans SC的ttf文件然后用fm.FontProperties(fnamefonts/SourceHanSansSC-Regular.otf)直接指定路径。这样不管换什么系统只要带上字体文件就能跑。Figure/Axes模型是matplotlib的精髓。学会显式创建和管理Axes后你会发现之前那些plt.subplot()的写法有多脆弱。特别是做多子图联动时Axes对象的方法比全局函数可控得多。颜色和字体是专业感的来源。花10分钟选一套配色比花1小时调参数更值。我常用的色板[#2C3E50, #E74C3C, #3498DB, #2ECC71, #F39C12, #9B59B6]基本覆盖了大多数场景。保存图片时用bbox_inchestight。这个参数会自动裁剪掉多余的空白边距避免图片四周出现大片白边。配合dpi300生成的图片直接可用于投稿或报告。下次遇到matplotlib的坑别急着搜Stack Overflow。先检查是不是字体问题再看是不是Axes没选对最后确认样式参数有没有被全局设置覆盖。这三个排查方向能解决90%的绘图异常。