三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Python 数据可视化:Matplotlib 与 Seaborn 学习笔记

Python 数据可视化:Matplotlib 与 Seaborn 学习笔记

完成 Pandas 的筛选、分组和统计后,下一步就是把数字转换成直观结论。数据可视化的重点并不是“图画得多漂亮”,而是选择正确的图表回答正确的问题。本篇复盘 Matplotlib 与 Seaborn 的常用图形、选图逻辑和图片导出。

一、先问问题,再选择图表

开始画图前,先明确这张图要回答什么问题。常见需求可以对应为:

分析问题推荐图表
随时间如何变化折线图line
不同类别谁高谁低柱状图bar
数据分布是什么形状直方图histplot
两个数值变量有什么关系散点图scatterplot
不同组的分布有何差异箱线图 / 小提琴图
多个数值变量如何相关热力图heatmap

一个简单原则是:折线看趋势、柱形做比较、直方看分布、散点看关系、热力图找相关线索。

二、Matplotlib 的 Figure 与 Axes

Matplotlib 中最重要的两个对象是FigureAxes。可以把Figure理解为整张画布,把Axes理解为画布中的一个坐标系。

import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(6, 4)) ax.plot([1, 2, 3], [2, 4, 3]) ax.set_title("Trend Demo") ax.set_xlabel("Time") ax.set_ylabel("Value") plt.show()

虽然plt.plot()等快捷写法适合临时画单图,但多子图时更推荐ax.plot()ax.bar()等面向对象写法,因为每一步都明确指定绘制到哪个坐标轴。

fig, axes = plt.subplots(1, 2, figsize=(8, 3)) axes[0].bar(["A", "B"], [3, 5]) axes[1].plot([1, 2, 3], [2, 1, 4]) fig.tight_layout()

Notebook 中反复创建大量图形时,可以在不再需要后执行plt.close(fig),减少图形对象堆积。

多子图是实际分析中非常常见的能力。例如plt.subplots(2, 2)会返回 2×2 的axes数组,可以用axes[0, 0]axes[1, 1]精确指定绘图位置。整张看板可以用fig.suptitle()设置总标题,各子图再分别设置自己的问题标题。这样比连续创建四张独立图片更容易形成一个完整的分析故事。

三、折线图与柱状图

折线图适合有天然顺序的数据,尤其是时间序列:

fig, ax = plt.subplots() ax.plot( ["Mon", "Tue", "Wed", "Thu"], [120, 150, 140, 180], marker="o", label="transactions", ) ax.set_ylabel("Transactions") ax.legend() ax.grid(True, alpha=0.3)

时间字段实际绘制前应先转换为datetime并按时间排序。城市、商品类别等无序类别不应强行用折线连接,否则视觉上会制造不存在的“变化趋势”。

柱状图更适合类别之间的大小比较。这里尤其要区分“人数”和“比例”:value_counts()得到数量,0/1 指标的mean()可以表示比例。标题和 y 轴必须明确到底展示的是count还是rate

例如 Titanic 的Survived只有 0 和 1,因此按舱位分组后求均值就是各舱位的生还率:

import pandas as pd titanic_demo = pd.DataFrame({ "Pclass": [1, 1, 2, 2, 3, 3], "Survived": [1, 1, 1, 0, 0, 0], }) rate = titanic_demo.groupby("Pclass")["Survived"].mean() fig, ax = plt.subplots() ax.bar(rate.index.astype(str), rate.values) ax.set_ylim(0, 1) ax.set_xlabel("Pclass") ax.set_ylabel("Survival rate")

比例图通常建议让 y 轴从 0 开始,避免通过截断坐标轴在视觉上夸大组间差异。

四、Seaborn:让统计图更省事

Seaborn 建立在 Matplotlib 之上,可以直接使用 DataFrame 的列名绘图,同时仍然可以通过ax继续设置标题、坐标轴等细节。

import pandas as pd import seaborn as sns df = pd.DataFrame({ "class": [1, 1, 2, 2, 3, 3], "survived": [1, 1, 1, 0, 0, 0], "sex": ["f", "m", "f", "m", "f", "m"], }) fig, ax = plt.subplots() sns.barplot( data=df, x="class", y="survived", hue="sex", errorbar=None, ax=ax, ) ax.set_ylabel("Survival rate")

hue可以增加一个分类维度,例如同时比较“舱位 × 性别”。但类别过多时颜色和图例会迅速变乱,此时更适合筛选 Top N、拆图或使用分面。

sns.barplot()与普通ax.bar()的一个重要区别是:前者可以直接基于原始 DataFrame 对 y 进行统计聚合。estimator决定使用均值等何种统计量,errorbar控制误差区间。在学习阶段如果只关注组间均值,可以设置errorbar=None;正式分析时是否保留误差区间,则应根据图表目的决定,不能把统计不确定性永久隐藏掉。

五、直方图与 countplot 不要混淆

直方图针对连续数值,通过分箱观察分布形态:

ages = pd.DataFrame({"Age": [18, 20, 21, 22, 30, 31, 45, 60]}) sns.histplot(data=ages, x="Age", bins=5, kde=True)

bins太少会掩盖结构,太多又会显得噪声过大,需要结合样本量调整。kde=True可以叠加平滑密度曲线,但不应把平滑曲线理解成原始数据本身。

countplot统计的是分类变量各类别的样本数量:

sns.countplot(data=df, x="class")

因此,histplot回答“连续变量如何分布”,countplot回答“每个类别有多少样本”。

如果想比较两个群体的连续分布,还可以给histplot增加hue。不过叠加的类别越多,图越难阅读。此时应考虑拆成多个子图,而不是不断增加颜色。直方图还可以配合kde=True查看平滑后的密度趋势,但 KDE 的形状会受到带宽等因素影响,不能把它当成真实观测频数。

六、散点图:观察两个数值变量的关系

points = pd.DataFrame({ "Age": [20, 30, 40, 50], "Fare": [10, 80, 30, 120], "Survived": [0, 1, 0, 1], }) sns.scatterplot( data=points, x="Age", y="Fare", hue="Survived", alpha=0.6, )

散点图主要观察趋势形态、聚类和离群点。数据点很多且重叠时,可以降低alpha。需要特别注意:图上出现关联只能作为进一步分析的线索,相关不等于因果

散点图中的hue同样可以编码第三个变量,例如用颜色区分是否生还。如果点数特别多,除了透明度,还可以先合理抽样或使用更适合高密度数据的表达方式。抽样后的图只能代表抽样数据,结论中也要明确这一限制。

七、箱线图、小提琴图与热力图

箱线图适合比较不同组的中位数、离散程度和潜在离群点;小提琴图进一步展示分布的密度形状。

sample = pd.DataFrame({ "group": ["A"] * 4 + ["B"] * 4, "value": [10, 12, 13, 30, 20, 21, 22, 23], }) fig, axes = plt.subplots(1, 2, figsize=(8, 3)) sns.boxplot(data=sample, x="group", y="value", ax=axes[0]) sns.violinplot(data=sample, x="group", y="value", ax=axes[1]) fig.tight_layout()

箱线图阅读时主要关注中位数、上下四分位数、四分位距和须外点。需要注意,箱线图中的“离群点”首先只是按照统计规则识别出的极端观测,并不自动等于错误数据;高票价、大额订单等极端值可能完全真实,是否删除仍需回到业务场景判断。

小提琴图把箱线统计与密度形态的思路进一步扩展,适合观察是否存在偏态、双峰等结构,但对于样本量很小的分组,平滑后的外形容易让人产生过度解读,因此最好同时关注每组样本数。

多个数值变量之间的线性相关可以先计算相关矩阵,再绘制热力图:

corr = points[["Age", "Fare", "Survived"]].corr() sns.heatmap( corr, annot=True, fmt=".2f", cmap="coolwarm", center=0, )

对角线恒为 1,正负号表示线性相关方向。热力图适合快速寻找线索,但不能直接用来证明变量之间存在因果关系。

计算相关矩阵前还要确认参与计算的是数值变量。类别编码成数字后虽然也能算相关系数,但数字大小是否具有真实的数量意义需要单独判断。例如Pclass的 1、2、3 有顺序含义,而把城市 A/B/C 随意编码成 1/2/3 后直接计算 Pearson 相关通常没有合理解释。

八、图中标注:让关键数据直接可见

当图中只有少量关键点或关键柱时,可以用annotate()增加说明,让读者不需要在坐标轴之间来回估算:

fig, ax = plt.subplots() values = [0.63, 0.47, 0.24] bars = ax.bar(["1", "2", "3"], values) for bar, value in zip(bars, values): ax.annotate( f"{value:.0%}", xy=(bar.get_x() + bar.get_width() / 2, value), ha="center", va="bottom", )

标注的原则仍然是“少而关键”。如果几十个点都贴文字,信息密度反而下降。标题适合表达主要结论,标注适合强调关键数值,图例负责解释颜色或线型,三者分工不要重复。

九、让图达到“可交付”标准

一张业务图至少应让读者快速知道:画的是什么、单位是什么、不同颜色代表什么。常用设置包括:

  • 使用结论型标题,而不是只写“柱状图”;

  • 设置xlabelylabel,必要时注明单位;

  • 多系列使用legend()

  • 长类别标签适当旋转;

  • 比例图通常从 0 开始,避免视觉夸大;

  • 多子图使用tight_layout()减少遮挡。

完成后可以直接导出图片:

fig, ax = plt.subplots() ax.plot([1, 2, 3], [2, 4, 3]) ax.set_title("Trend Demo") fig.savefig( "visualization.png", dpi=150, bbox_inches="tight", )

推荐顺序是:创建图 → 绘制 → 美化 → 保存 → 关闭bbox_inches="tight"可以减少标题或标签被裁切的问题。

十、中文字体与图片显示问题

在 Windows 或 Notebook 中绘制中文标题时,偶尔会出现方框、乱码或Glyph missing警告。这通常不是数据问题,而是当前 Matplotlib 找不到能够显示中文的字体。可以根据电脑已经安装的字体设置候选列表:

plt.rcParams["font.sans-serif"] = [ "Microsoft YaHei", "SimHei", "DejaVu Sans" ] plt.rcParams["axes.unicode_minus"] = False

字体名称必须与系统中真实安装的字体一致;换到 Linux、Colab 或服务器后,Windows 字体可能不存在,因此发布或部署前需要在目标环境重新检查。axes.unicode_minus=False主要用于避免坐标轴负号显示异常。

另外,图片看起来“挤”时不要只增加figsize。应先检查标题、图例、刻度标签是否过长,再配合tight_layout()、标签旋转或减少不必要文字解决。图的目标是快速传递信息,而不是把所有分析结果同时塞进一张画布。

十一、大数据绘图:先聚合,再展示

Notebook 中的 Store Sales 数据量较大,这类数据不适合直接把每一行都塞进折线图。更合理的流程是:读取必要字段或适量样本 → 解析日期 → 按日期、门店等维度聚合 → 排序 → 绘图。

transactions = pd.DataFrame({ "date": ["2026-01-01", "2026-01-01", "2026-01-02"], "transactions": [100, 120, 150], }) transactions["date"] = pd.to_datetime(transactions["date"]) daily = transactions.groupby("date", as_index=False)["transactions"].sum() fig, ax = plt.subplots() sns.lineplot(data=daily, x="date", y="transactions", marker="o", ax=ax)

这里“先聚合”不仅能减少绘图数据量,也让每个点具有清晰业务含义。如果为了速度使用nrows只读取文件前若干行,那么图表描述必须写明这是样本期或截取数据,不能把局部趋势直接当成完整数据集结论。

十二、交付前的图表验收

一张图准备放进 CSDN、报告或周报前,可以快速检查:

  1. 标题能否直接说明图回答的问题;

  2. x/y 轴是否有名称,数量、比例、金额是否注明含义或单位;

  3. 颜色是否确实编码了有用的分类信息;

  4. 图例、标签和中文是否完整可读,没有被边界裁切;

  5. 比例尺是否会夸大差异,数据是否经过错误排序;

  6. 图中的结论是否超出了数据能够支持的范围;

  7. 导出的 PNG 能否正常打开,分辨率是否满足发布需求。

这个验收过程比继续堆颜色、阴影和特效更重要。可视化的最终标准是:即使读者不看正文,也能基本理解这张图在比较什么,以及它能够支持什么结论。

十三、常见错误总结

常见错误正确思路
用折线连接无序类别类别比较使用柱状图
把直方图和柱状图混为一谈连续变量看 hist,类别看 bar/count
countplot当比例图countplot 表示样本数量
多子图混用裸plt.plot()明确使用axes[i].plot()
图中没有标题、单位和图例先保证读者能独立理解
热力图相关直接写成因果相关只作为分析线索
show()不保存交付图使用savefig()

总结

数据可视化的核心不是掌握多少种图,而是建立“问题 → 数据类型 → 图表 → 读图结论”的思考方式。Matplotlib 提供底层布局与精细控制,Seaborn 更适合快速绘制基于 DataFrame 的统计图。真正有效的图表应该一眼看懂、含义明确,并且不会通过错误的图形选择制造误导。

← 返回列表