Python Matplotlib基础绘图实例:折线图、柱状图与饼图代码
详解Python Matplotlib绘制折线图、柱状图与饼图的完整代码实例,涵盖中文字体配置、多序列折线趋势图、并列分组柱状图、百分比饼图、多子图组合排版及高清图片导出方法。
假设你刚整理完一份月度业务统计表,需要把产品销量随时间的变化、不同产品线的月度对比以及各区域营收占比制作成图表插入报告。在 Python 中使用 matplotlib.pyplot 模块,直接调用 plot()、bar() 和 pie() 三个函数即可生成标准折线图、柱状图和饼图。本文围绕这一场景,提供从环境准备、中文字体设置到三类基础图表独立实现与多子图合并导出的完整代码,并说明关键参数的作用与常见避坑要点。
一、环境准备与中文显示配置
在编写绘图代码前,需要先确认 Python 环境中已安装 Matplotlib 库,并完成中文字体与负号显示的基础配置。如果直接用默认配置绘制包含中文标题或坐标轴标签的图表,Matplotlib 默认加载的英文字体(DejaVu Sans)缺少中文字符集,会导致画面上的中文全部变成方框。
1. 安装与版本检查
打开系统终端(Windows 使用命令提示符或 PowerShell,macOS 与 Linux 使用 Terminal),在已激活的 Python 虚拟环境或全局环境中执行以下命令安装并验证 Matplotlib:
pip install matplotlib
python -c "import matplotlib; print(matplotlib.__version__)"
3.8.4
2. 通用绘图流程与基础配置代码
无论是绘制折线图、柱状图还是饼图,Matplotlib 的标准代码结构都遵循固定的五个阶段:准备数据、创建画布与配置字体、调用绘图函数、添加标题与坐标轴标注、保存文件或弹窗展示。下图展示了这一标准执行流程:
为了让代码在 Windows、macOS 和常见 Linux 发行版上都能正常显示中文,可以在代码开头传入一个包含多平台常见中文字体的回退列表,Matplotlib 会按顺序查找系统中第一个可用的字体:
import matplotlib.pyplot as plt
# 依次尝试加载苹方(macOS)、微软雅黑(Windows)、思源黑体/文泉驿(Linux)、黑体
plt.rcParams["font.sans-serif"] = [
"PingFang SC",
"Microsoft YaHei",
"Noto Sans CJK SC",
"WenQuanYi Micro Hei",
"SimHei",
]
# 解决更换中文字体后坐标轴负号“-”显示为方块的问题
plt.rcParams["axes.unicode_minus"] = False
接下来的三个小节将各自提供一个独立、完整的 Python 脚本。你可以将任意一段代码保存为 .py 文件(例如 plot_demo.py)并在终端运行 python plot_demo.py,或者直接粘贴到 Jupyter Notebook 单元格中执行。
二、折线图代码实例:展示连续趋势与多序列对比
折线图通过线段将按顺序排列的数据点连接起来,最适合反映数据随时间(如月份、季度、小时)或有序数值变化的趋势。当需要同时对比两条或多条业务线的走势时,可以在同一个坐标系内多次调用 plt.plot(),并通过颜色、线型和标记点加以区分。
1. 理解画布(Figure)与坐标系(Axes)的层级结构
在阅读具体代码前,需要先明确 Matplotlib 图表的内部组成关系:最外层是窗口画布 Figure,画布内部包含实际承载数据的绘图区 Axes,而标题、坐标轴刻度、网格线、数据折线和图例都附着在 Axes 之上。下图展示了这些组件在折线图中的对应位置:
2. 双序列折线图完整代码
下面这段代码绘制了 1 至 6 月“移动端”与“桌面端”月度活跃用户数的走势对比,并为移动端折线的每个节点添加了具体数值标签:
import matplotlib.pyplot as plt
# 1. 配置中文字体与负号显示
plt.rcParams["font.sans-serif"] = ["PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", "SimHei"]
plt.rcParams["axes.unicode_minus"] = False
# 2. 准备横轴与两条纵轴序列数据
months = ["1月", "2月", "3月", "4月", "5月", "6月"]
mobile_mau = [82, 96, 120, 115, 142, 165] # 移动端月活(万人)
desktop_mau = [64, 68, 79, 86, 94, 102] # 桌面端月活(万人)
# 3. 创建画布,宽8英寸、高5英寸,分辨率100 DPI
plt.figure(figsize=(8, 5), dpi=100)
# 4. 绘制两条折线
plt.plot(
months,
mobile_mau,
color="#2b7a78",
linestyle="-",
linewidth=2.2,
marker="o",
markersize=6,
label="移动端",
)
plt.plot(
months,
desktop_mau,
color="#5c8ab5",
linestyle="--",
linewidth=2.0,
marker="s",
markersize=6,
label="桌面端",
)
# 5. 在移动端折线的数据点上方添加具体数值
for x_idx, y_val in enumerate(mobile_mau):
plt.text(
x_idx,
y_val + 3.5,
str(y_val),
ha="center",
va="bottom",
fontsize=10,
color="#1e293b",
)
# 6. 设置标题、坐标轴标签、Y轴范围、图例与网格线
plt.title("上半年各端月度活跃用户趋势", fontsize=14, pad=12)
plt.xlabel("统计月份", fontsize=11)
plt.ylabel("月活量(万人)", fontsize=11)
plt.ylim(40, 185) # 预留顶部空间,防止最高点的数值文字超出边框
plt.grid(True, linestyle="--", alpha=0.35)
plt.legend(loc="upper left", frameon=True)
# 7. 自动紧凑布局并保存、展示
plt.tight_layout()
plt.savefig("line_chart.png", dpi=150)
plt.show()
line_chart.png 文件,并弹出图形预览窗口:画面包含一条带圆点的深青色实线(移动端)和一条带方块的蓝灰色虚线(桌面端),移动端每个转折点正上方标有 82 至 165 的具体数字,左上角显示图例框。
3. 关键参数解释
marker与linestyle:如果只画折线而不加marker,读者很难看清数据采样点究竟落在哪个位置。常用标记点包括圆点'o'、方块's'和三角形'^';常用线型包括实线'-'、虚线'--'和点划线'-.'。plt.text(x, y, s)的坐标定位:当横轴传入的是字符串列表(如["1月", "2月", ...])时,Matplotlib 内部会将它们依次映射为整数索引0, 1, 2, ...。因此遍历enumerate(mobile_mau)得到的x_idx可以直接作为横坐标传入plt.text()。plt.ylim(40, 185):如果不手动调大 Y 轴上限,最高点165上方偏移绘制的文字标签很容易贴到甚至超出图表顶部边框。
三、柱状图代码实例:对比离散类别与并列分组数据
柱状图利用柱子的高度表示数值大小,适合对比相互独立的离散类别(如不同产品线、不同部门、不同城市)。实际业务中除了单组柱状图,更常见的是将两个时期的数值并排放在同一个类别刻度下进行对比,即“并列分组柱状图”。
1. 并列柱状图的坐标偏移原理
如果在同一个坐标系里直接调用两次 plt.bar(categories, values),后画的柱子会完全覆盖在先画的柱子正上方。要让两组柱子左右并排,必须先用整数序列 [0, 1, 2, ...] 表示各类别的基准中心位置 x,再设定单根柱子的宽度 width(例如 0.35),将第一组柱子的中心向左移动半个柱宽(x - width / 2),第二组柱子的中心向右移动半个柱宽(x + width / 2)。下图直观展示了这一偏移机制及饼图的角度切分结构:
2. 并列分组柱状图完整代码
以下代码对比了四个核心产品线在第一季度(Q1)与第二季度(Q2)的销售额,并使用 Matplotlib 3.4 及以上版本内置的 plt.bar_label() 函数一次性完成所有柱顶数值的标注:
import matplotlib.pyplot as plt
# 1. 配置中文字体与负号显示
plt.rcParams["font.sans-serif"] = ["PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", "SimHei"]
plt.rcParams["axes.unicode_minus"] = False
# 2. 准备类别名称与两个季度的销售额数据(单位:万元)
products = ["云服务器", "对象存储", "云数据库", "内容分发"]
q1_sales = [120, 98, 145, 76]
q2_sales = [156, 134, 168, 92]
# 3. 计算横轴基准位置与左右偏移坐标
x_base = list(range(len(products))) # [0, 1, 2, 3]
bar_width = 0.35
x_q1 = [i - bar_width / 2 for i in x_base]
x_q2 = [i + bar_width / 2 for i in x_base]
# 4. 创建画布并绘制两组并列柱状图
plt.figure(figsize=(8, 5), dpi=100)
bars_q1 = plt.bar(x_q1, q1_sales, width=bar_width, color="#83ADD0", label="第一季度 (Q1)")
bars_q2 = plt.bar(x_q2, q2_sales, width=bar_width, color="#2b7a78", label="第二季度 (Q2)")
# 5. 使用 bar_label 在每根柱子顶部自动添加数值(padding=3 表示向上留白3磅)
plt.bar_label(bars_q1, padding=3, fontsize=10, color="#334155")
plt.bar_label(bars_q2, padding=3, fontsize=10, color="#0f172a")
# 6. 将横轴刻度重新绑定回中心基准位置 x_base,并替换为中文产品名
plt.xticks(x_base, products, fontsize=11)
plt.ylabel("销售额(万元)", fontsize=11)
plt.title("各产品线 Q1 与 Q2 销售额对比", fontsize=14, pad=12)
plt.ylim(0, 195)
plt.grid(axis="y", linestyle="--", alpha=0.35) # 仅开启水平方向参考线
plt.legend(loc="upper right")
# 7. 保存并展示
plt.tight_layout()
plt.savefig("bar_chart.png", dpi=150)
plt.show()
bar_chart.png 并显示窗口:横轴 4 个产品名称正上方各有一浅一深两根并排柱子,每根柱子顶端居中显示对应的具体数值,背景仅保留水平虚线网格以便对齐高度。
plt.xticks(x_base, products) 将类别文字标签绑定在未偏移的中心坐标 x_base 上。如果把类别列表直接传给某一次 plt.bar(),横轴刻度文字就会偏左或偏右,无法居中对齐两组柱子的中间缝隙。如果只需要绘制单组柱状图,则无需计算偏移,直接调用 plt.bar(products, q2_sales, width=0.5) 即可。
四、饼图代码实例:呈现部分占整体的比例关系
饼图将一个完整圆盘按数值比例切分为多个扇区,用于表达“各个组成部分占总体的百分比”。绘制饼图时不需要传入已经算好的百分比,只需传入原始数值列表,plt.pie() 会自动求和并计算每个类别的占比。
1. 突出重点扇区的饼图完整代码
以下代码展示了四个大区的年度营收占比,并将占比最高的“华东区”向外轻微抽离以突出显示:
import matplotlib.pyplot as plt
# 1. 配置中文字体与负号显示
plt.rcParams["font.sans-serif"] = ["PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", "SimHei"]
plt.rcParams["axes.unicode_minus"] = False
# 2. 准备分类标签与原始营收数值(无需手动换算为百分比)
regions = ["华东区", "华北区", "华南区", "西南区"]
revenues = [450, 300, 150, 100]
# 设置每个扇区向外抽离的距离(相对于半径的比例),0表示不抽离
explode_dist = (0.06, 0, 0, 0)
colors_palette = ["#2b7a78", "#5c8ab5", "#83ADD0", "#a8c6df"]
# 3. 创建画布并绘制饼图
plt.figure(figsize=(7, 5.5), dpi=100)
wedges, texts, autotexts = plt.pie(
revenues,
explode=explode_dist,
labels=regions,
colors=colors_palette,
autopct="%1.1f%%", # 自动计算百分比并保留1位小数
startangle=90, # 从12点钟方向(90度)开始逆时针排列
pctdistance=0.65, # 百分比文字距圆心的相对距离
wedgeprops={"edgecolor": "#ffffff", "linewidth": 1.5}, # 扇区间添加白色分隔线
textprops={"fontsize": 11, "color": "#1e293b"},
)
# 将深色扇区内的百分比文字设为白色以保证清晰可读
autotexts[0].set_color("#ffffff")
autotexts[1].set_color("#ffffff")
# 4. 固定坐标轴纵横比为1:1,确保饼图为正圆
plt.axis("equal")
plt.title("各区域年度营收构成占比", fontsize=14, pad=14)
plt.legend(wedges, regions, title="业务大区", loc="lower right")
# 5. 保存并展示
plt.tight_layout()
plt.savefig("pie_chart.png", dpi=150)
plt.show()
pie_chart.png:画面中央为一个正圆形饼图,扇区之间带有清晰的白色描边间隔;占比 45.0% 的“华东区”从 12 点钟方向起算并向左上角微微分离,每个扇区内部显示带一位小数的百分比数值。
2. 饼图的适用条件与限制
- 分类数量不宜超过 6 个:当分类过多或存在大量占比低于 3% 的细碎项时,饼图扇区会变得极窄,导致文字标签互相重叠。遇到这种情况,应先将细小分类合并为“其他”,或者直接改用水平条形图(
plt.barh())。 - 不能包含负数或全零数据:饼图表达的是非负部分对整体的贡献,如果数据列表中含有负数,
plt.pie()会直接抛出ValueError: Wedge sizes 'x' must be non negative values。
五、组合子图与高清导出:在同一画布中汇总三类图表
在实际制作数据看板或分析报告时,经常需要把折线图、柱状图和饼图并排放在同一张图片里输出。此时可以使用 fig, axes = plt.subplots(1, 3, figsize=(15, 4.8)) 一次性创建包含 1 行 3 列子图的画布,再分别在 axes[0]、axes[1] 和 axes[2] 上调用对应的绘图方法。
ax 上设置标题和坐标轴名称时,需要将 plt.title()、plt.xlabel()、plt.ylabel() 改为带 set_ 前缀的方法:ax.set_title()、ax.set_xlabel() 和 ax.set_ylabel()。而绘图函数本身(ax.plot()、ax.bar()、ax.pie())名称保持不变。
import matplotlib.pyplot as plt
# 1. 全局配置中文字体与负号
plt.rcParams["font.sans-serif"] = ["PingFang SC", "Microsoft YaHei", "Noto Sans CJK SC", "SimHei"]
plt.rcParams["axes.unicode_minus"] = False
# 2. 创建 1行3列 的子图画布
fig, axes = plt.subplots(1, 3, figsize=(15, 4.8), dpi=100)
# --- 子图1:折线图 ---
months = ["1月", "2月", "3月", "4月", "5月", "6月"]
mau = [82, 96, 120, 115, 142, 165]
axes[0].plot(months, mau, color="#2b7a78", marker="o", linewidth=2)
axes[0].set_title("(a)移动端月活趋势", fontsize=13)
axes[0].set_xlabel("月份")
axes[0].set_ylabel("月活量(万人)")
axes[0].grid(True, linestyle="--", alpha=0.35)
# --- 子图2:柱状图 ---
products = ["云服务器", "对象存储", "云数据库", "内容分发"]
sales = [156, 134, 168, 92]
bars = axes[1].bar(products, sales, width=0.5, color="#5c8ab5")
axes[1].bar_label(bars, padding=3)
axes[1].set_title("(b)Q2 各产品线销售额", fontsize=13)
axes[1].set_ylabel("销售额(万元)")
axes[1].set_ylim(0, 195)
axes[1].grid(axis="y", linestyle="--", alpha=0.35)
# --- 子图3:饼图 ---
regions = ["华东区", "华北区", "华南区", "西南区"]
revenues = [450, 300, 150, 100]
axes[2].pie(
revenues,
labels=regions,
autopct="%1.1f%%",
startangle=90,
explode=(0.06, 0, 0, 0),
colors=["#2b7a78", "#5c8ab5", "#83ADD0", "#a8c6df"],
wedgeprops={"edgecolor": "#ffffff", "linewidth": 1.2},
)
axes[2].set_title("(c)各区域营收占比", fontsize=13)
axes[2].axis("equal")
# 3. 添加总标题并自动调整子图间距,导出裁剪白边的高清PNG
fig.suptitle("业务核心指标综合看板", fontsize=16, fontweight="bold", y=0.98)
plt.tight_layout()
plt.savefig("dashboard_summary.png", dpi=200, bbox_inches="tight")
plt.show()
dashboard_summary.png,画面从左至右依次排列折线图、柱状图与饼图,顶部居中显示总标题“业务核心指标综合看板”,三幅子图之间间距均匀、无文字重叠。
六、三类基础图表选型与核心参数总结
在日常数据分析中,选择哪种图表取决于你要回答的核心问题:看时间走势选折线图,看分类高低对比选柱状图,看部分占总体的份额选饼图。下图与对照表汇总了三类图表的判断路径与常用参数:
| 图表类型 | 调用函数 | 输入数据要求 | 关键修饰参数 | 不建议使用的情况 |
|---|---|---|---|---|
| 折线图 | plt.plot(x, y) |
x 为有序序列(时间或递增数值),y 为等长数值序列 |
linestyle、marker、linewidth、label |
横轴是无先后顺序的独立类别(如不同城市)时,连线会产生误导 |
| 柱状图 | plt.bar(x, height) |
x 为类别名称或坐标索引,height 为对应数值 |
width、color、bottom、配合 plt.xticks() |
类别名称很长且数量超过 12 个时,建议改用水平条形图 plt.barh() |
| 饼图 | plt.pie(x) |
x 为非负一维数值列表(自动求和算占比) |
labels、autopct、explode、startangle |
包含负数、各部分比例极其接近,或细碎分类超过 6 个 |
七、常见绘图问题排查
1. 保存到本地的图片打开后是一片空白
表现与原因:弹窗预览时图表完全正常,但打开 plt.savefig() 生成的 PNG 文件却只有纯白背景。这是因为代码里把 plt.show() 写在了 plt.savefig() 前面。plt.show() 在窗口关闭后会清空并释放当前活动的画布,后续再调用保存函数时实际上是在保存一张新建的空白画布。
修复方法:始终坚持“先保存、后展示”的顺序,把 plt.savefig("chart.png", dpi=150, bbox_inches="tight") 放在 plt.show() 的上一行。
2. 导出的图片边缘文字或图例被裁切掉一半
表现与原因:当标题字号较大、横轴文字旋转过角度,或者把图例放在了坐标系外侧时,默认导出的图片边框可能截断边缘文字。
修复方法:在绘图结束前调用 plt.tight_layout() 让 Matplotlib 自动重算边距;同时在保存图片时传入 bbox_inches="tight" 参数,使其根据画面上所有可见元素的实际外框自动包裹裁切。
3. Linux 服务器环境下设置字体后中文依然显示为方块
表现与原因:在没有安装桌面环境的 Linux 服务器或 Docker 容器中,系统自带字体库往往不包含黑体或微软雅黑,并且终端会打印 UserWarning: findfont: Font family ... not found。
修复方法:先在终端执行 fc-list :lang=zh 查看当前系统已安装的中文字体名称;若返回为空,可安装开源中文字体包(如 Ubuntu/Debian 下安装 fonts-noto-cjk 或 fonts-wqy-microhei),随后删除 Matplotlib 的字体缓存目录(可通过 import matplotlib; print(matplotlib.get_cachedir()) 查看具体路径),重新运行脚本即可生效。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













