Matplotlib 入门:常见图表、子图布局与绘图流程

从基础绘图流程、折线图、柱状图到样式设置、子图布局与图片保存,建立 Matplotlib 可视化的最小入门框架。

数据分析通常不会停留在“算出结果”,最后往往还要把结果展示出来。Matplotlib 就是 Python 生态中最经典、最基础的可视化库。

很多更高级的可视化库,本质上也和 Matplotlib 有密切关系,所以它很值得作为第一站学习。

一、为什么需要可视化

同一组数据,直接打印数字和画成图,理解成本通常完全不同。

例如你要看:

  • 销量是上涨还是下跌
  • 不同城市之间差距有多大
  • 某些值是否明显异常
  • 数据分布是集中还是分散

这些问题,用图往往比看纯文本更直观。

二、Matplotlib 的基本使用流程

最常见的写法是:

import matplotlib.pyplot as plt

这里的 pyplot 可以理解为“一组便捷画图接口”。

把基础绘图流程记成四步:

  1. 准备横轴和纵轴数据
  2. 选择图表类型,例如 plot() 画折线
  3. 添加标题和坐标轴说明
  4. 调用 show() 展示

一个最常见的入门示例:

import matplotlib.pyplot as plt

months = ["Jan", "Feb", "Mar", "Apr"]
sales = [120, 150, 130, 180]

plt.plot(months, sales)      # 根据横轴和纵轴数据画出折线
plt.title("Monthly Sales")   # 添加图表标题
plt.xlabel("Month")          # 设置横轴名称
plt.ylabel("Sales")          # 设置纵轴名称
plt.show()                   # 把图表窗口显示出来

matplotlib 图例

这段代码已经包含了 Matplotlib 入门最常用的一整套操作:导包、画折线、补充标题和坐标轴标签、最后显示图表。

💡 提示:中文显示问题 如果你在标题或标签中使用了中文(如 plt.title("月度销量")),默认情况下可能会显示为方块(乱码)。 解决方法是在代码开头加上两行配置:

#按顺序尝试可用字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False    # 确保负号正常显示

这组 rcParams 配置是 Matplotlib 中很常见的中文显示修复方式,适合初学者先掌握。

💡 不弹窗、图形窗口打不开问题 如果你遇到 plt.show() 不弹窗、图形窗口打不开,那通常是后端问题,不属于中文乱码本身。 此时才可能需要显式指定后端,而且必须写在导入 pyplot 之前,例如:

import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt

TkAgg 更适合本地桌面脚本环境,在 Jupyter、远程服务器或无图形界面的环境中并不一定适用。

三、折线图:看趋势

折线图最适合看“随时间变化”的趋势,比如月销量、日温度、网站访问量等连续变化的数据。

上面的基础示例本身就是折线图。使用 plt.plot(x, y) 时,Matplotlib 会按照数据顺序把各个点连接起来,从而表现整体走势。

折线图里最常见的几个参数有:

  • marker="o":给每个数据点加上标记,便于观察具体位置
  • color="blue":设置线条颜色
  • linestyle="--":设置线条样式,例如虚线
  • label="Sales":给当前线条命名,后续可配合 plt.legend() 显示图例

如果数据本质上是在比较离散类别,而不是观察连续趋势,那么通常优先考虑柱状图会更合适。

四、柱状图:看类别对比

如果你要比较不同类别的数值,柱状图通常更合适。

import matplotlib.pyplot as plt

cities = ["Beijing", "Shanghai", "Shenzhen"]
orders = [320, 410, 360]

plt.bar(cities, orders)
plt.title("Orders by City")
plt.xlabel("City")
plt.ylabel("Orders")
plt.show()

运行效果示意:

Matplotlib 柱状图示例

柱状图很适合展示:

  • 各城市销量对比
  • 各产品销售额对比
  • 各班级平均分对比

五、饼图:看占比

如果你更关心“整体里各部分各占多少”,饼图会比柱状图更直观。

import matplotlib.pyplot as plt

labels = ["Food", "Rent", "Transport", "Other"]
values = [35, 40, 15, 10]

plt.pie(
    values,
    labels=labels,
    autopct="%1.1f%%",
    startangle=90,
)
plt.title("Monthly Expense Ratio")
plt.axis("equal")  # 保证饼图是正圆,而不是椭圆
plt.show()

饼图示例

这里常见的几个参数含义是:

  • labels:每一块对应的类别名称
  • autopct:在图上显示百分比格式
  • startangle:控制起始绘制角度,让图形更便于阅读
  • axis("equal"):让横纵坐标比例一致,保证图形不变形

饼图更适合展示“组成占比”,例如:

  • 月度支出结构
  • 各渠道流量占比
  • 各产品销售额占比

如果类别太多,或者各项数值差异很小,饼图就不如柱状图清楚,这时优先考虑 bar() 会更稳妥。

六、散点图:看相关性

当你想观察两个变量之间有没有某种关系时,散点图很有用。

import matplotlib.pyplot as plt

study_hours = [1, 2, 3, 4, 5, 6]
scores = [55, 60, 68, 72, 80, 88]

plt.scatter(study_hours, scores)
plt.title("Study Hours vs Score")
plt.xlabel("Study Hours")
plt.ylabel("Score")
plt.show()

运行效果示意:

Matplotlib 散点图示例

如果点大致沿着某个方向分布,你就能直观看到变量之间的趋势关系。

七、直方图:看分布

当你想看一组数据大致集中在哪些区间、是否分布均匀时,直方图会很有帮助。

import matplotlib.pyplot as plt

scores = [55, 60, 62, 68, 70, 72, 75, 80, 82, 88, 90, 95]

plt.hist(scores, bins=5, edgecolor="black")
plt.title("Score Distribution")
plt.xlabel("Score")
plt.ylabel("Count")
plt.show()

运行效果示意:

Matplotlib 直方图示例

这里:

  • bins=5 表示把数据大致分成 5 个区间
  • edgecolor="black" 让每个柱子的边界更清楚

八、设置样式与图例

import matplotlib.pyplot as plt

months = ["Jan", "Feb", "Mar", "Apr"]
product_a = [120, 150, 140, 180]
product_b = [100, 130, 160, 170]

plt.plot(months, product_a, label="Product A", color="blue", linestyle="-")
plt.plot(months, product_b, label="Product B", color="green", linestyle="--")

plt.title("Sales Comparison")
plt.xlabel("Month")
plt.ylabel("Sales")
plt.legend()
plt.show()

常见可调项包括:

  • color 颜色
  • linestyle 线型
  • marker 点样式
  • label 图例名称

九、figureaxes

后面你会逐渐接触:

  • figure:整张画布
  • axes:画布中的具体子图区域(图里真正画线、画柱子的区域)

例如:

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.plot([1, 2, 3], [2, 4, 3])
ax.set_title("Demo")
plt.show()

这种写法在多子图场景下更常见,也更适合后续进阶。

十、图表大小与子图布局

很多时候一张图还不够,你可能想把多个图放在同一行里对比,同时控制图片大小。

import matplotlib.pyplot as plt

months = ["Jan", "Feb", "Mar", "Apr"]
sales = [120, 150, 130, 180]
profit = [30, 45, 35, 60]

fig, axes = plt.subplots(1, 2, figsize=(10, 4))

axes[0].plot(months, sales, marker="o")
axes[0].set_title("Sales")

axes[1].bar(months, profit)
axes[1].set_title("Profit")

plt.tight_layout()
plt.show()

运行效果示意:

Matplotlib 子图布局示例

这段代码里:

  • figsize=(10, 4) 控制整张图的宽和高
  • plt.subplots(1, 2) 表示创建 1 行 2 列的子图
  • tight_layout() 用来自动调整间距,避免标题或标签挤在一起

十一、保存图片

很多时候我们不只是看图,还要把图保存到文件。

import matplotlib.pyplot as plt

x = [1, 2, 3]
y = [3, 5, 4]

plt.plot(x, y)
plt.title("Example")
plt.savefig("example.png", dpi=150)

常见注意点:

  • savefig() 一般放在 show() 前面
  • dpi 越高,导出图片通常越清晰
  • 文件格式可以是 pngjpgsvgpdf

十二、和 Pandas 联动的常见方式

在数据分析里,Matplotlib 往往不是直接手写全部数据,而是接收 Pandas 处理后的结果。

常见分析流程通常是:

  1. Pandas 读取和清洗数据
  2. groupby 或聚合得到结果
  3. Matplotlib 把结果画成图

案例:

# TODO 需求: 获取中美日三个国家 GDP 数据, 并绘制每年变化折线图
# 1. 导包
import matplotlib
matplotlib.use("TkAgg")  # 解决个别本地环境 show() 不弹窗的问题,必须写在 pyplot 之前

import matplotlib.pyplot as plt
import pandas as pd

# 解决中文乱码和负号显示问题
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False

# 2. 读取数据
# 注意:以后更常见的是通过读取文件生成 DataFrame 对象
df = pd.read_csv("data/1960-2019全球GDP数据.csv", encoding="gbk", sep=",")

# 3. 先了解数据
print(df.shape)  # 形状 (9931, 3)
# print(df.ndim)      # 维度
# print(df.head())    # 如果不传参数,默认显示前 5 行
# print(df.tail())    # 如果不传参数,默认显示后 5 行
# print(df.columns)   # 获取所有列名
# print(df.count())   # 各列的非空数据个数
# df.info()           # 查看基本信息

# 4. 数据预处理
df.dropna(inplace=True)
print(df.shape)  # 形状 (9930, 3)

# 5. 数据分析
# TODO 需求 1:先查询中美日的 GDP 数据
#加copy()就不会有提醒,否则会提醒:SettingWithCopyWarning: 
#A value is trying to be set on a copy of a slice from a DataFrame
df_cn = df[df["country"] == "中国"].copy()
df_us = df[df["country"] == "美国"].copy()
df_jp = df[df["country"] == "日本"].copy()

# TODO 需求 2:把年份作为索引列
df_cn.set_index("year", inplace=True)
df_us.set_index("year", inplace=True)
df_jp.set_index("year", inplace=True)

# TODO 需求 3:把 GDP 列名依次修改为中国、美国、日本
df_cn.rename(columns={"GDP": "中国"}, inplace=True)
df_us.rename(columns={"GDP": "美国"}, inplace=True)
df_jp.rename(columns={"GDP": "日本"}, inplace=True)

# TODO 需求 4:可视化展示中美日的每年变化折线
plt.title("1960-2019 年中美日 GDP 数据折线图")
plt.plot(df_cn.index, df_cn["中国"], label="中国", color="red")
plt.plot(df_us.index, df_us["美国"], label="美国", color="blue")
plt.plot(df_jp.index, df_jp["日本"], label="日本", color="green")
plt.legend()  # 添加图例
plt.grid()    # 添加网格
plt.show()    # 手动展示

"""
样例数据
1960-2019全球GDP数据.csv
year,country,GDP
1960,美国,543300000000 
1960,英国,73233967692 
1960,法国,62225478000 
1960,中国,59716467625 
1960,日本,44307342950 
1960,加拿大,40461721692 
1960,意大利,40385288344 
1960,印度,37029883875 
1960,澳大利亚,18577668271 
"""
图片

# 1.导包
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 解决个别版本show()无法展示图的问题
import matplotlib

matplotlib.use('TkAgg')
# 解决中文乱码问题
plt.rcParams['font.sans-serif'] = ['SimHei']

# 额外安装: pip install openpyxl
# 2.加载数据
df = pd.read_excel("data/sales_test.xlsx", index_col='USERID')
# print(df)
# print(df.index)
print(df.shape)  # (9, 3)
# 3.数据预处理
# 删除全部为空的行
sale_data = df.dropna(how='all')
print(sale_data.shape)
print(sale_data.columns)
# 4.数据分析
# todo 1.获取每个用户最近的购买时间,购买频率,购买金额
F_data = sale_data.groupby(sale_data.index)['ORDERID'].count()
M_data = sale_data.groupby(sale_data.index)['AMOUNTINFO'].sum()
R_data = sale_data.groupby(sale_data.index)['ORDERDATE'].max()
# print(R_data)
# print(F_data)
# print(M_data)
print('===========================================================')
# TODO 2.分别计算RFM的分数
F_score = pd.cut(F_data, 5, labels=[1, 2, 3, 4, 5])
M_score = pd.cut(M_data, 5, labels=[1, 2, 3, 4, 5])
# 设置一个基础时间,计算R分数
base_date = pd.to_datetime("2022-04-01")
R_days = (R_data - base_date).dt.days
R_score = pd.cut(R_days, 5, labels=[1, 2, 3, 4, 5])
# print(R_score)
# print(F_score)
# print(M_score)
print('===========================================================')
# TODO 3.合并分数
rfm_list = [R_score, F_score, M_score]
rfm_cols = ['r_score', 'f_score', 'm_score']
# 注意: .transpose()把3行6列转换为6行3列
rfm_df = pd.DataFrame(np.array(rfm_list).transpose(), dtype=np.int32, columns=rfm_cols, index=R_data.index)
print(rfm_df)
# TODO 4.加权得分
# 商家根据自己的需求:设置关注用户交易时间,次数,金额的权重
# 比如: 我设置交易时间:20%,交易总次数:20%,交易总金额:80%
rfm_df['rfm_w_score'] = rfm_df['r_score'] * 0.2 + rfm_df['f_score'] * 0.2 + rfm_df['m_score'] * 0.6
# TODO 5.1 分析结果可视化
# 自定义分层阈值,设置会员等级
bins = [0, 2, 3, 5]
labels = ['低', '中', '高']
# 打分
rfm_df['客户分类'] = pd.cut(rfm_df['rfm_w_score'], bins=bins, labels=labels)
print(rfm_df)
# 绘制柱状图
rfm_df['客户分类'].value_counts().plot(kind='bar',color=['red','green','pink'], rot=0)
plt.title('RFM客户分群')
plt.xlabel('会员等级')
plt.ylabel('会员数量')
plt.grid()
plt.show()


# TODO 5.2 分析结果保存到本地
rfm_df.to_csv('data/rfm_score.txt')

#样例数据:data/sales_test.xlsx
"""
        ORDERDATE  ORDERID  AMOUNTINFO
USERID                                
1.0    2023-05-01     11.0      1000.0
2.0    2023-04-02     22.0      2000.0
3.0    2023-04-03     33.0      3000.0
4.0    2023-04-04     44.0      4000.0
5.0    2023-04-05     55.0      5000.0
6.0    2023-04-05     66.0         NaN
2.0    2022-04-02     77.0       300.0
NaN           NaT      NaN         NaN
2.0    2022-04-23     88.0       800.0
6.0    2022-04-23     99.0      1500.0
NaN           NaT      NaN         NaN
"""

图片

小结

Matplotlib 解决的是“把分析结果直观展示出来”的问题。只要掌握基础绘图流程、常见图表类型和几个常用配置项,你就已经能完成大量日常分析展示任务。

它的学习门槛不算最低,但回报非常高,因为几乎所有数据分析工作最终都绕不开可视化表达。