Matplotlib.pyplot从入门到顺手:避坑指南与多子图布局详解
2026/9/10 16:34:38 网站建设 项目流程

绘图的坑,我帮你踩过了:Matplotlib.pyplot 从入门到顺手

我见过太多人第一次跑数据可视化,卡在module 'matplotlib' has no attribute 'pyplot'这类报错上,或者装了半天库,import matplotlib.pyplot as plt依然红一片。说实话,作为一个用 Python 做数据分析、平时出图表比写文档还勤快的人,我太清楚 Matplotlib 这套东西对新手有多劝退了——文档不算友好,默认样式又丑,网上教程东一榔头西一棒子,真正能落地解决问题的干货反而分散。

所以这篇我就把 Matplotlib.pyplot 这套绘画体系,从安装、核心概念、常用图表的实操写法,到“六张图合在一个图里”这种多子图布局、中文乱码和样式美化这些高频痛点,一次性串起来讲。不管你是刚接触数据可视化的学生,还是工作中被各种报表逼着画图的职场人,这篇文章都能让你少走弯路,照着手敲就能跑出图来。

1. 先把基础夯实:matplotlib 的安装与“面向对象”思维

1.1 安装那点事,别让环境劝退你

matplotlib 的安装本身不算复杂,但很多人恰恰死在这一步。常见的pip install matplotlib报错,多半是网络源太慢、Python 版本太老,或者当前环境压根没激活对。我习惯在装任何 Python 包之前,先敲一句:

python -m pip install --upgrade pip

把 pip 自己先升到最新,然后再装 matplotlib:

python -m pip install matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple

这里用的是清华镜像源,国内下载速度快非常多。如果你用的是 Anaconda 发行版,那更省事,conda 环境里一般自带 matplotlib,直接在终端里输入:

conda install matplotlib

很多新手会遇到“明明装了却还是ModuleNotFoundError: No module named 'matplotlib'”,这种十有八九是环境混了。比如你在终端里pip install装了一个,然后在 Jupyter Notebook 或者 IDE 里用的是另一个 Python 解释器。排查方法很简单,在报错的那个环境里重新跑一下安装命令,或者直接在代码里打印:

import sys print(sys.executable)

看看当前解释器路径,再去命令行里pip show matplotlib确认包的安装路径,两者能对上就基本没问题。这个坑我踩了不止一次,尤其是换了新电脑、装了多个 Python 版本的时候,特别容易中招。

1.2 pyplot 是什么?它和你画的图是什么关系

import matplotlib.pyplot as plt里的pyplot,说白了是 matplotlib 提供的一个面向过程的接口模块。它像一个“画板管家”,你调用一次plt.plot(),它就在当前画布上画一笔;你调用plt.show(),它就把画布弹出来给你看。这个机制类似你在纸上先画草图,草稿不用留,最后成品满意了再精心装裱。

但真正让我从“会用”到“用明白”的,是理解了 Matplotlib 的面向对象接口。简单理解:

  • Figure:整张画布/纸张,所有元素都贴在它上面。
  • Axes:画布上的一个绘图区域,也就是一个“子图”,你实际看到的数据、坐标轴、刻度都是 Axes 的属性。
  • Axis:就是 X 轴、Y 轴本身,控制刻度、标签等细节。

刚开始用plt.plot(x, y)这种写法时,你以为在往“全局画布”上画东西,其实 pyplot 在背后自动帮你创建了一个 Figure 和一个 Axes。等你要在一个图里放六个子图的时候,只有把 Figure 和 Axes 的概念搞明白,才能随心所欲地排兵布阵。所以后面讲子图布局之前,我会先带你把这个模型吃透。

2. 从零画出一张图:折线图与散点图实战

2.1 你的第一张折线图:代码背后发生了什么

折线图大概是 matplotlib 里最常用、也最被低估的图表。很多人用plt.plot(x, y)画完就拉倒,但里面的参数值得玩味。我先给出一个最朴素的版本:

import matplotlib.pyplot as plt import numpy as np x = np.linspace(0, 10, 100) y = np.sin(x) plt.plot(x, y) plt.title("y = sin(x)") plt.xlabel("x") plt.ylabel("y") plt.grid(True) plt.show()

这段代码会画出一条正弦波。但你可能好奇:np.linspace(0, 10, 100)干了什么?它生成从 0 到 10 之间均匀分布的 100 个点。因为正弦函数是连续曲线,你不可能真的画“无限”个点,用有限点连成折线就足够平滑了。100 个点在 0~10 这个区间上非常平滑,根本看不出是折线;如果只给 10 个点,曲线就会呈现明显的锯齿感。

plt.plot(x, y)里面还有很多好用的参数。比如你想把线的颜色改成红色,线型改成虚线,点用圆形标记:

plt.plot(x, y, color='red', linestyle='--', marker='o', markersize=4)

这里的marker控制坐标点上的标记样式,markersize控制标记大小。我一般这么记:plot函数有通用的样式参数,还有对应的缩写写法,比如'r--o'就代表红色虚线加圆形标记。缩写在快速画图时很高效,但代码可读性会下降,合作项目里我会写成完整参数名,方便别人看懂。

2.2 散点图:当数据凌乱时更需要被看见

散点图的出场场景往往是“两个变量之间有没有相关性”。比如分析身高和体重的关系、广告投入和销售额的关系,用散点图一眼就能看出趋势。

x = np.random.randn(200) y = np.random.randn(200) * 0.5 + x * 0.3 plt.scatter(x, y, alpha=0.6, s=30, c='steelblue') plt.title("Scatter Plot Demo") plt.xlabel("X") plt.ylabel("Y") plt.show()

这里alpha=0.6是透明度。当散点特别多、重叠特别严重的时候,不加透明度你会看到一团黑,加了透明度就能看到分布密度差异。s是点的大小,c是颜色。这些参数看似简单,但真正决定一张散点图好不好看、能不能传递信息的,往往是这些细节。

我自己的习惯是,散点图尽量配合趋势线一起用。直接用np.polyfit拟合一条一次函数,再叠加画到图上,比单纯丢一堆点上去更有说服力。

import numpy as np coeff = np.polyfit(x, y, deg=1) trend = np.polyval(coeff, x) plt.scatter(x, y, alpha=0.6, s=30, c='steelblue', label='data') plt.plot(x, trend, color='red', linewidth=2, label='trend') plt.legend() plt.show()

np.polyfit的作用就是做最小二乘拟合,deg=1表示一次线性拟合,返回的是斜率与截距组成的数组。再用np.polyval把拟合系数代回去,算出每个 x 对应的拟合值。这样图上有原始点、有趋势线、有图例,一眼就能跟老板交代“这两列数据到底有没有关系”。

3. 柱状图与饼图:业务汇报里的“发言担当”

3.1 柱状图:别忽略基准线这个细节

柱状图在业务汇报里几乎是刚需。比如你手里有三个城市的销售额数据,想直观比大小:

cities = ['Beijing', 'Shanghai', 'Guangzhou'] sales = [120, 165, 90] plt.bar(cities, sales, color=['#4C72B0', '#DD8452', '#55A868']) plt.ylabel("Sales (万元)") plt.title("City Sales Comparison") plt.show()

plt.bar第一个参数是分类轴的位置,第二个是高度,也就是数值大小。颜色我习惯用 matplotlib 内置的配色 cycler,不用自己调得太费神,这几个十六进制色值是来自 seaborn 风格的颜色,看起来比默认的蓝橙绿和谐不少。

柱状图有一个常被忽略但很关键的参数:bottom。如果你想做“堆积柱状图”,比如一个季度里线上销售额和线下销售额分别多少,叠加起来看总量,这个参数就非常有用:

import numpy as np quarters = ['Q1', 'Q2', 'Q3'] online = [30, 42, 38] offline = [50, 48, 45] plt.bar(quarters, online, label='Online') plt.bar(quarters, offline, bottom=online, label='Offline') plt.ylabel("Sales") plt.legend() plt.show()

我踩过一个坑:堆积柱状图的bottom参数如果没对齐好,两个分类的数据会“错位叠加”,看起来特别奇怪。比如online有 3 个数据、offline有 3 个数据,必须确保bottom里的序列长度和当前柱子的序列长度一致。如果长度不一致,matplotlib 不会报错,但你的图会悄悄画错地方,而且不仔细看根本发现不了。

3.2 饼图:三个容易翻车的坑

饼图是一个看着简单、实际很容易翻车的图。小问题在于标签重叠,大问题在于信息表达不清。一张饼图超过 5 个类别,我建议你换个图表类型,或者把次要类别合并成一个“其他”。

一个相对稳的写法:

labels = ['A', 'B', 'C', 'D'] sizes = [45, 30, 15, 10] colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'] explode = (0, 0.1, 0, 0) # 只有第二个扇形突出 plt.pie(sizes, labels=labels, colors=colors, autopct='%1.1f%%', startangle=90, explode=explode, shadow=False) plt.axis('equal') plt.show()

autopct='%1.1f%%'控制扇形内显示的百分比格式,保留一位小数。startangle=90是可以转饼图起始角度,我习惯从 90 度开始,这样第一块扇形在正上方,读起来更顺。explode是让某一块“炸开”,适合强调重点类别。

但有一类问题很隐蔽:plt.pie默认会自动按数据顺序从逆时针方向排列扇形,如果数据本身不是按大小排的,你可能想手动调整顺序。我一般先给数据排序再传进去,否则图上一团乱麻,用户看半天也不知道哪个占比大。另一个坑是plt.axis('equal')千万别漏,不写的话饼图会被拉成椭圆,视觉上比例全错了。

4. 六张图合在一个图上:子图布局的精髓

4.1 用 plt.subplots 直接切分画布

很多热搜词会指向“matplotlib 六张图合在一个图上”,这其实是多子图布局问题。最推荐的方式是plt.subplots(),它比plt.subplot()更 Pythonic,而且返回的fig, axes结构让你可以分别控制每个子图。

fig, axes = plt.subplots(2, 3, figsize=(12, 8))

这行代码生成了一个 2 行 3 列的画布网格,共 6 个子图。figsize控制整张图的宽高,单位是英寸。2 行 3 列在我实践下来是六张图里最舒服的排列,横向 3 张不会太拥挤,纵向 2 行也能保证高度合理。如果你把figsize设成 (6, 4),每个子图会非常局促,标签和标题互相挤压,图基本没法看。

接下来向每个子图填充内容:

import numpy as np x = np.linspace(0, 10, 100) fig, axes = plt.subplots(2, 3, figsize=(12, 8)) axes[0, 0].plot(x, np.sin(x)) axes[0, 0].set_title("sin(x)") axes[0, 1].plot(x, np.cos(x)) axes[0, 1].set_title("cos(x)") axes[0, 2].plot(x, np.tan(x)) axes[0, 2].set_ylim(-5, 5) axes[0, 2].set_title("tan(x)") axes[1, 0].plot(x, x**2) axes[1, 0].set_title("x^2") axes[1, 1].plot(x, np.exp(x)) axes[1, 1].set_title("exp(x)") axes[1, 2].plot(x, np.log(x + 1)) axes[1, 2].set_title("log(x+1)") plt.tight_layout() plt.show()

注意axes是一个二维数组,axes[0, 0]是第一行第一列,axes[0, 1]是第一行第二列,依此类推。不熟悉的人总是把axes[0]当成第一个子图,用着用着就错了。知道它是二维结构以后,你就把每个axes[i, j]当作一个独立的“小画布”,调用它的plot()set_title()等方法来绘制内容就行。

真正常见的错误是:画完六个子图后,图与图之间贴得太紧,标题和刻度标签都挤在一起。解决办法就是加上plt.tight_layout()这一行,它会让 matplotlib 自动调整子图间距,效果立竿见影。我在所有多子图布局里都会加这一句,基本上不会翻车。

4.2 更灵活的方式:add_subplot 与 GridSpec

subplots虽好,但它只能画规则的均匀网格。如果我想让第一个子图横跨两列,或者某个子图更高一些,就得用add_subplotGridSpec了。

add_subplot适合“手动划分但仍在网格内”的场景。比如我想让左边一个大图,右边上下两个小图:

fig = plt.figure(figsize=(10, 6)) ax1 = fig.add_subplot(1, 2, 1) ax2 = fig.add_subplot(2, 2, 2) ax3 = fig.add_subplot(2, 2, 4) ax1.plot(x, np.sin(x)) ax1.set_title("Left Large Plot") ax2.plot(x, np.cos(x), 'tab:orange') ax2.set_title("Top Right") ax3.plot(x, np.exp(x / 3), 'tab:green') ax3.set_title("Bottom Right") plt.tight_layout() plt.show()

fig.add_subplot(1, 2, 1)表示把画布划分成 1 行 2 列,选中第 1 个位置。fig.add_subplot(2, 2, 2)表示划分成 2 行 2 列,选中第 2 个位置。这样左右区域在网格里错开,就能实现“左边一个大图,右边上下两个小图”的效果。

GridSpec更强大,支持自定义每个子图跨度的精细控制。它做复杂仪表盘时非常好用,但多数人日常用subplots就够了。我建议先把subplots练熟,碰到需求超出“均匀网格”的范围时,再上GridSpec去攻,这样学起来更平滑。

5. 中文乱码与外观美化:别让图被“丑”拦住

5.1 中文显示问题的两条出路

matplotlib 默认字体不包含中文字符,所以一画图,中文就变成一个个小方框,非常影响美观。以前我都是用rcParams把所有字体全局改成宋体或者黑体:

plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False

第一行是把默认无衬线字体改为黑体,第二行是让负号正常显示。设完这个,中文标题、标签都不会乱码。这个做法的缺点是,如果你换一台电脑,或者生成的图片交给别人用他自己的环境跑,字体不存在就会失效。所以更稳妥的办法是直接指定字体路径:

import matplotlib.pyplot as plt from matplotlib import font_manager font_path = '/System/Library/Fonts/PingFang.ttc' font_prop = font_manager.FontProperties(fname=font_path) plt.rcParams['font.family'] = font_prop.get_name()

font_manager.FontProperties可以直接加载指定路径下的字体文件,不依赖系统是否安装。fname参数就是字体文件路径,你可以找到系统里任一一个中文字体,把路径往这一填,全局字体就变了。这个方式在公司服务器、或者多人协作时最稳,因为它把“这台机器有没有这个字体”的依赖彻底绕开了。

5.2 调整配色、坐标轴和网格线的实战技巧

默认的 matplotlib 样式确实很“工程风”,蓝底、白刻度、灰边框,放在论文里没问题,但放在大屏演示或者自有博客上,就差点意思。我常用的策略是直接用内置的style主题:

plt.style.use('seaborn-v0_8-whitegrid')

不同 matplotlib 版本可用的 style 名稍有差异,比如老版本是'seaborn',新版本可能是'seaborn-v0_8-whitegrid'。如果use时报错,可以先执行:

print(plt.style.available)

把所有可用的 style 名列出来,挑一个你喜欢的。我试过ggplotfivethirtyeightseaborn-v0_8-darkgrid,最终用得最多的是带网格的seaborn-v0_8-whitegrid,原因很简单:它对数据点的位置阅读非常友好,又不会像深色主题那样“喧宾夺主”。

坐标轴视角的调整也很技巧。比如画正切函数,默认 y 轴范围会拉到正负无穷,图完全没法看,所以我上面才加了set_ylim(-5, 5)。用一个日志量级的数据,我还会用set_xscale('log')。用对坐标轴尺度,要比单纯换颜色更能解决“图看不清楚”的问题。

plt.grid(True)是打开网格线,alpha=0.3是让网格变淡,不至于抢了数据本身的风头。这些都是细节,但正是这些细节决定了你的图是“能用”还是“好看”。

6. 猜你想问:Matplotlib 高频报错与实用排查

6.1 加载不出图、看不见图、图一闪而过

“代码运行了没有报错,但图没弹出来”是一个高频问题。如果你在终端脚本里调用plt.show()后图一闪而过,大概率是脚本执行完就退出了,窗口也跟着关了。我一般会在脚本结尾加一句:

plt.show() input("Press Enter to exit...")

让窗口等你敲回车再关闭。如果你在 Jupyter Notebook 里,图像显示不出来,通常是魔法命令没加,在 cell 最上面加:

%matplotlib inline

%matplotlib inline是让 matplotlib 图像直接内嵌到 Jupyter 的输出区域里,而不是弹出独立窗口。头一次用 Jupyter 没加这句,图就会“跑丢”,但其实它被输出到底层了,不立即可见,特别容易让人摸不着头脑。

还有一个容易被忽略的点:如果你在服务器上跑代码,没有图形界面,plt.show()就什么也不会显示。这时候你需要用:

fig.savefig('output.png', dpi=150, bbox_inches='tight')

把图直接保存成文件,再下载到本地看。dpi控制分辨率,bbox_inches='tight'会裁掉多余的留白,让图片更紧凑。这个组合我基本天天用,尤其批量出图的时候,全部通过savefig落盘,效率极高。

6.2 图例、刻度、以及保存时被裁剪的坑

图例的位置loc是一个容易翻车的细节。默认是右上角'best',但你有时候希望固定图例位置,比如loc='upper left'或者loc='lower right'。当数据点很多、图例和曲线重叠在一起时,我会加frameon=False去掉图例边框,或者稍微调小图例文字透明度,这样整体看起来更干净。

刻度标签过多也是麻烦。如果 x 轴有 50 个类别,全部显示出来就会叠成黑黑一团。我常用:

plt.xticks(rotation=45, ha='right')

让标签旋转 45 度,避免互相遮挡;ha='right'表示右对齐,这样旋转后的文字看着更整齐。如果你的数据是时间序列,直接像下面这样让 matplotlib 自动挑选刻度间距,通常比手动指定更聪明:

fig.autofmt_xdate()

保存图片被裁剪是另一个常见的坑。如果你代码里写了plt.savefig('myplot.png'),但标题或者某个标签被切了半个,那就是你没有设置bbox_inches='tight'。我在生成任何交付用图时都会写:

plt.savefig('myplot.png', dpi=200, bbox_inches='tight')

dpi=200保证放大后不糊,bbox_inches='tight'保证所有元素完整落进图片。这条经验是我被朋友提醒过之后才长记性的,现在它已经写进了我的代码备忘录。

6.3 性能问题:绘制太多点卡死怎么办

当你的数据量非常大,比如有几百万个散点,直接用plt.scatter画,matplotlib 会卡到你怀疑人生。scatter的设计目标是支持每个点不同大小、不同颜色,这些额外负担在大数据量下会被无限放大。这个时候我一般换用plt.plot加上标记来画散点图:

x = np.random.randn(1000000) y = np.random.randn(1000000) # 快很多 plt.plot(x, y, '.', markersize=1, alpha=0.3)

'.'表示用单个像素点作为标记,markersize=1进一步缩小点的大小。实测下来,百万级数据的绘制速度能提升好几个数量级。如果数据量再大,还可以用np.histogram2d先做密度统计,然后用imshow画热力图,这才是正解。可视化不是硬画,而是用合适的图表达合适的数据密度。

7. 数据可视化的“好习惯”与扩展方向

7.1 一个万能的绘图模板,直接抄作业

用到现在,我自己总结了一套模板,基本能满足 90% 的日常需求。你可以直接复制改改:

import matplotlib.pyplot as plt import numpy as np # 全局设置 plt.rcParams['figure.dpi'] = 120 plt.rcParams['font.sans-serif'] = ['SimHei'] plt.rcParams['axes.unicode_minus'] = False fig, ax = plt.subplots(figsize=(8, 5)) # 数据准备 x = np.linspace(0, 10, 100) y1 = np.sin(x) y2 = np.cos(x) # 绘图 ax.plot(x, y1, linewidth=2, label='sin(x)') ax.plot(x, y2, linewidth=2, label='cos(x)', linestyle='--') ax.set_xlabel("x") ax.set_ylabel("y") ax.set_title("Sin and Cos Curves") ax.legend(loc='upper right') ax.grid(True, alpha=0.3) plt.tight_layout() plt.savefig('sin_cos.png', dpi=150, bbox_inches='tight') plt.show()

figure.dpi是控制整个图片输出分辨率的全局参数,这个模板里设成 120 是我反复权衡后的值:观察窗口清晰、保存文件体积也合理。你换成figsize=(10, 6)就能得到更宽的长宽比。整套模板拆解下来,无非就是“设置全局→准备数据→选择 Axes 绘图→标注细节→保存显示”,思路清晰后,任何图都只是变这几个环节。

7.2 接下来说说往 Pandas 和 Seaborn 方向的自然延伸

matplotlib 只是基础绘图库,真正的生产力提升来自和 Pandas、Seaborn 的结合。Pandas 的 DataFrame 直接提供.plot()方法,底层就是调用 matplotlib:

import pandas as pd df = pd.DataFrame({'x': x, 'sin': np.sin(x), 'cos': np.cos(x)}) df.plot(x='x', y=['sin', 'cos'], figsize=(8, 5)) plt.show()

df.plot(x='x', y=['sin', 'cos'])会自动生成图例,并对 DataFrame 里的列名做映射,比逐个传递数组要省事不少。碰到数据探索阶段,我基本都是这个流程:先df.describe()看统计口径,再用df.plot()快速画一遍粗图,最后才用 matplotlib 精细调整样式。

Seaborn 更高级,它是基于 matplotlib 的封装,画统计图特别方便:

import seaborn as sns tips = sns.load_dataset('tips') sns.scatterplot(data=tips, x='total_bill', y='tip', hue='time')

sns.scatterplothue参数可以按另一个类别字段自动着色,并且自动添加图例。这个功能用纯 matplotlib 去写会非常繁琐,但用 Seaborn 一行就搞定了。如果你做数据分析报告,强烈建议先学 matplotlib 打底,再学 Seaborn 提效。

7.3 用动画展示数据变化,其实没那么玄乎

matplotlib 的 animation 模块我一开始觉得特别复杂,但只要理解“每一帧重新绘制一次”这个本质,就发现也挺顺手。比如想让一条正弦曲线动起来,可以这样做:

import matplotlib.animation as animation fig, ax = plt.subplots() x = np.linspace(0, 2 * np.pi, 100) line, = ax.plot(x, np.sin(x)) def update(frame): line.set_ydata(np.sin(x + frame / 10)) return line, ani = animation.FuncAnimation(fig, update, frames=100, interval=50) plt.show()

关键在于line.set_ydata()只更新 y 数据,而不是从头plot一次,这样性能才扛得住。FuncAnimation会反复调用update函数,每次传一个frame参数,你就可以用这个帧编号来控制数据的变化。interval=50表示每 50 毫秒更新一帧,这个速度播放起来比较顺滑,不会太焦急也不会卡顿。

动画很适合做时序数据的展示,比如传感器数值的实时曲线回放,或者模拟数据的变化过程。虽然日常静态图已经能覆盖绝大多数场景,但掌握了 animation,你会发现述职汇报里放一段动态变化图,效果比干巴巴的静态图好太多。

8. 回顾与一个实用建议

我自己最早接触 matplotlib 时,也走了一些弯路。那时候什么都是从网上抄代码,抄是抄会了,但始终没有理解“Figure-Axes”这一对核心概念,导致一碰到复杂布局就两眼一抹黑。后来我强迫自己每画一张图,都先问一句“我现在是在操纵 Figure 还是 Axes?”,这个习惯帮我节省了大量时间。

如果你想在这个基础上继续深入,我建议你把官方文档里的 Canvas 示例挨个跑一遍,特别是subplotsGridSpec的组合用法。另一点是留意apply_autoscaleset_xlimset_aspect这些坐标轴细节,它们解决的虽然是“小问题”,但在真实工作中,正是这些小问题决定了你的图表是让人眼前一亮,还是让人皱眉头。

画图这件事,和写代码一样,多练才有手感。打开你的编辑器,先跑通上面的模板,再试着把里面的数据换成你自己的,一步步来,你很快就能把 matplotlib.pyplot 用成顺手的小工具。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询