拿到一批数据,想用Python画个图看看分布和趋势,结果代码跑通了一看又丑又乱,这是很多朋友初学可视化时的共同经历。我最早接触matplotlib时也踩过不少坑,折腾一阵才发现:统计图的坑大多不在语法上——做错选择、参数没配好、把不该画的图画错了才是问题。所以这篇东西不打算按API文档那样逐条列,而是把常用统计图的适用场景、源代码实现、输出效果含义和避坑经验放在一起,做成一个“查表即用”的汇总。内容覆盖matplotlib、seaborn和pandas自带绘图,基于我实际项目中经常用到的场景来写。无论你是刚接触Python数据分析,还是已经有基础但总在出图上卡壳,照着代码走一遍,应该能解决你八成以上的画图需求。
1. 画图前先想明白:常见统计图到底在解决什么问题
1.1 选择图表不是看哪个“好看”,而是看你想表达什么
我见过不少新手一上来就用折线图,结果X轴根本不是连续型数据,画出来跟心电图似的毫无意义。选统计图的核心逻辑只有一条:先确定你的数据是什么类型,再确定你想让读者看到什么结论。比如你想看一个变量的分布是否集中,直方图比其他图都直接;想比较几个类别的数值高低,柱状图最清楚;想观察两个变量之间的相关关系,散点图永远是最佳起点。
拿我自己的经验举例:做销售数据分析时,团队里经常有人问“本月各区域的销售额差距大不大”,这种问题就应该画箱线图,它把最小值、下四分位、中位数、上四分位、最大值全部压缩在一张图里,分布形态和异常点一览无余。如果只是随便画个柱状图,就只能看到每个点的高低,看不到背后的离散程度,信息损耗非常严重。
1.2 六种常用统计图的适用场景速查表
这张表是我自己整理后一直保存在笔记里的,每次画图前都会先过一遍:
| 图类型 | 适用数据 | 核心问题 | 常用工具 |
|---|---|---|---|
| 折线图 | 时间序列、连续变量 | 数值随顺序如何变化 | matplotlib, pandas |
| 柱状图 | 类别数据、离散变量 | 各类别数值差异有多大 | matplotlib, seaborn |
| 散点图 | 两个连续变量 | 变量之间是否有相关关系 | matplotlib, seaborn |
| 直方图/密度图 | 单个连续变量 | 数据的分布形态如何 | matplotlib, seaborn |
| 箱线图 | 类别+连续变量 | 分布和异常值情况如何 | seaborn, matplotlib |
| 饼图 | 类别占比数据 | 部分占整体的比例 | matplotlib |
需要特别说明的是饼图。很多可视化专家不推荐饼图,因为人眼对面积的感知并不精确,当类别超过5个或者占比差异不明显时,饼图就会变得很难读。我个人的建议是:分类不超过5个、且只看占比时用饼图没问题;一旦涉及精确对比,还是回归柱状图,或者用环形图来改善可读性。
2. matplotlib快速上手:一张图背后的三层结构
2.1 Figure(画板)、Axes(画纸)、Axis(刻度)的分工
很多教程一上来就甩plt.plot(),不是说不能用,但如果你不理解背后结构,后面做子图、调坐标轴就会一头雾水。换个生活化的说法:Figure 是整个大白板,Axes 是白板上的一张张画纸,Axis 是画纸边缘的刻度尺。一张画板上可以贴多张画纸区分不同图表,每张画纸各自带上下左右四把刻度尺。
下意识去看下面的骨架代码,几乎所有matplotlib自定义操作都能落在这三层的任一层上:
import matplotlib.pyplot as plt # 创建一个画板figure,以及里面的1行1列第1张画纸axes fig, ax = plt.subplots(figsize=(8, 5)) # 在这张画纸上画线 ax.plot([1, 2, 3, 4], [10, 20, 15, 30]) # 修改这张画纸的标题和坐标轴标签 ax.set_title("示例折线图") ax.set_xlabel("时间点") ax.set_ylabel("数值") # 展示画板 plt.show()这里的 ax 对象才是你真正打交道的对象,它拥有plot、scatter、bar、hist、boxplot等全套绘图方法。之前用plt.xxx系列函数其实是一个简洁入口,背后仍然会创建 Figure 和 Axes,只是隐藏了细节。理解这层差异之后,你会发现复杂的论文图、商业报告图其实都是由多个Axes组合出来的。
2.2 基础配置:字体、画布尺寸、分辨率和风格
代码写得再漂亮,图一出来全是方块字,这是中文系统的经典问题。根本原因是matplotlib默认字体里没有中文字符。我的惯例是每次写分析脚本时,头几行固定填一份字体配置:
import matplotlib.pyplot as plt # 微软雅黑在Windows系统下很常见,Linux服务器上可换成文泉驿或Noto Sans CJK plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "WenQuanYi Zen Hei"] plt.rcParams["axes.unicode_minus"] = False # 解决负号显示成方块的问题两个参数一个管字体回退,一个管负号正常显示,建议直接放到脚本首部,全局生效。画布尺寸和分辨率我会在创建画板时一并处理:
fig, ax = plt.subplots(figsize=(10, 6), dpi=100)- figsize 单位是英寸,横向10英寸、纵向6英寸,约等于4:3的宽高比,适合大多数场景。如果用默认值
(6.4, 4.8),图一旦挤字段就会被拉得很憋屈。 - dpi 是每英寸点数,屏幕展示100就够了,要打印或者放论文里建议调到200以上,线条和文字才不会发虚。
风格上matplotlib提供了几个内置主题,plt.style.use("ggplot")是深色背景、网格线条风格,"seaborn-v0_8-whitegrid"比较清爽,我日常用白底网格居多,因为网格线能帮读者更快对齐数值。
2.3 一步到位的保存图片技巧
保存图片时用plt.savefig()而不是截图,可以保证尺寸和分辨率完全可控。注意这个操作要在plt.show()之前或者在同一个画布仍然存活的时候做,否则可能存到空文件。我常用的写法是:
fig.savefig("output/销售趋势图.png", dpi=200, bbox_inches="tight")bbox_inches="tight"会自动裁剪掉多余白边,让图片四周贴合坐标轴和标题,发到文档里不会留一大片空白。
3. 常见统计图的源代码示例与效果解读
3.1 折线图:趋势与时间序列的主场
折线图的核心场景是连续变量随时间或顺序变化。我用一份模拟的月度销售额数据来做演示。直接从pandas生成DataFrame,然后用matplotlib画图,这是实际工作中最高频的流程:
import numpy as np import pandas as pd import matplotlib.pyplot as plt plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei"] plt.rcParams["axes.unicode_minus"] = False # 构造数据:12个月,模拟递增趋势加一点波动 months = [f"{i}月" for i in range(1, 13)] sales = [12.5, 13.8, 15.2, 14.7, 16.9, 18.3, 19.6, 20.1, 21.5, 23.8, 25.2, 27.6] df = pd.DataFrame({"月份": months, "销售额": sales}) fig, ax = plt.subplots(figsize=(10, 5)) ax.plot(df["月份"], df["销售额"], marker="o", linewidth=2, label="月销售额") ax.set_title("2024年月度销售额趋势", fontsize=15) ax.set_xlabel("月份") ax.set_ylabel("销售额(万元)") ax.grid(True, linestyle="--", alpha=0.5) ax.legend() fig.tight_layout() plt.show()代码其实不难,关键点有三个:
marker="o"让每个数据点上显示圆点标记,不然只有一根线,读者很难对应具体数值。linewidth=2加粗曲线,小尺寸预览时也能看清走势。grid(True, linestyle="--", alpha=0.5)加虚线网格,透明度不要太高,否则喧宾夺主。
从输出图里,读者第一眼能抓住的是销售额大体在持续上升,中间4月出现一次小回落,之后保持涨势。这就是折线图最擅长的事情——趋势比具体数值更醒目。如果图里有多个系列,要记得用label区分,并补上ax.legend()图例,否则多条线会混成一团。
3.2 柱状图:类别对比的第一选择
柱状图适合横轴是非连续类别、纵轴是数值的对比场景。还是用销售数据,这次看各产品线的销量对比如下:
categories = ["手机", "笔记本", "平板", "耳机", "智能手表"] quantities = [320, 245, 187, 510, 268] fig, ax = plt.subplots(figsize=(9, 5)) # 用不同的温和颜色填充,并去除柱子边框 bars = ax.bar(categories, quantities, color=["#5B9BD5", "#ED7D31", "#A5A5A5", "#70AD47", "#FFC000"]) # 数据标签:在每根柱子上方显示数值 for bar in bars: height = bar.get_height() ax.text(bar.get_x() + bar.get_width()/2, height + 8, f"{height}", ha="center", va="bottom", fontsize=10) ax.set_title("各产品线季度销量对比", fontsize=15) ax.set_ylabel("销量(件)") ax.grid(axis="y", linestyle="--", alpha=0.5) plt.show()这个例子加入了两个实用增强:
- 每根柱子顶部用
ax.text()标出数值,省去读者从坐标轴反推数值的麻烦。bar.get_x()拿到柱子左下角x坐标,再加上bar.get_width()/2就能定位到柱子正中线上方。 grid(axis="y")只画水平网格线,竖线省去,版面看起来更干净。
柱状图最常见的错误有三个:柱宽设置不合理导致柱子之间挤压或过疏;颜色太花哨导致视觉焦点分散;类别顺序没有排序,导致读者看不出高低规律。我的经验是:如果不是要刻意按自然顺序(比如月份)展示,最好按数值从高到低排序再画。排过序的柱状图信息传递效率远高于乱序。
3.3 散点图:关联关系的侦察兵
散点图用来判断两个连续变量是否相关。之前做过一次广告投入和销售额的关系分析,数据是模拟的,但规律很典型:
np.random.seed(42) n = 80 ad_spend = np.random.uniform(5, 60, n) # 广告投入,万元 sales = 8 + 1.8 * ad_spend + np.random.normal(0, 6, n) # 销售额,万元 fig, ax = plt.subplots(figsize=(8, 6)) ax.scatter(ad_spend, sales, alpha=0.7, s=40, edgecolor="white", linewidth=0.5) ax.set_title("广告投入与销售额的散点图", fontsize=15) ax.set_xlabel("广告投入(万元)") ax.set_ylabel("销售额(万元)") # 拟合一条回归线,看更直观 slope, intercept = np.polyfit(ad_spend, sales, 1) x_fit = np.linspace(ad_spend.min(), ad_spend.max(), 50) ax.plot(x_fit, slope * x_fit + intercept, color="red", linestyle="--", linewidth=2) plt.show()np.random.seed(42)的作用是让随机序列可复现,你跑代码也能得到同样的点,这是数据分析和教学演示的好习惯。
从散点图上可以直观看出:数据点沿着左下到右上的方向聚集,说明广告投入和销售额正相关。我再用np.polyfit做了一阶多项式拟合,把趋势线叠加到图上,这种叠加会让相关性更加清晰。alpha=0.7控制点透明度,数据点重叠多的时候,透明度的价值非常明显——不透明的点叠在一起就是一团黑,透明的点能看出浓度差异。
3.4 直方图与密度图:分布形态的X光片
当你想了解一个变量的分布是否均匀、是否偏向某一侧、是否存在异常峰,直方图是最直接的。比如分析用户在线时长的分布,用模拟数据演示:
np.random.seed(7) session_minutes = np.concatenate([ np.random.normal(25, 8, 400), # 主体用户集中在25分钟左右 np.random.normal(65, 10, 50) # 少数重度用户集中在65分钟左右 ]) fig, ax = plt.subplots(figsize=(9, 5)) ax.hist(session_minutes, bins=25, color="#70AD47", alpha=0.7, edgecolor="white") ax.set_title("用户在线时长分布直方图", fontsize=15) ax.set_xlabel("在线时长(分钟)") ax.set_ylabel("用户人数") plt.show()bins=25表示把数据分成25个区间。bins选的过大,直方图会变成锯齿状;选得过小,又看不清细节。我个人经验是“小数据量区间尽量少于样本量的平方根,大数据量用30-50个区间基本不会翻车”。从上图可以明显看出一个主峰值和一个次峰值,说明用户群里存在两种截然不同的使用习惯——这就是直方图的发现能力。
如果想看更平滑的分布曲线,可以叠加核密度估计。seaborn对这类任务支持非常好:
import seaborn as sns sns.set_theme(style="whitegrid") fig, ax = plt.subplots(figsize=(9, 5)) sns.histplot(session_minutes, bins=25, kde=True, color="#4472C4", ax=ax) ax.set_title("用户在线时长分布(直方图+密度曲线)", fontsize=15) ax.set_xlabel("在线时长(分钟)") ax.set_ylabel("频数 / 密度") plt.show()注意直方图和密度图在y轴单位上不一样:直方图统计的是落在每个区间的样本数,密度曲线下的总面积是1。所以如果两个图画在同一张图上,y轴刻度不能直接互通阅读,读者只需要看图形态、不需要读具体数值时问题不大,做正式的汇报图时需要留意这一点。
3.5 箱线图:分布和异常值一网打尽
箱线图一个盒子就把中位数、四分位数范围、异常值全部交代清楚了。它特别适合“类别对比+分布差异”的组合场景。比如按不同运营方案对比用户转化率:
np.random.seed(10) # 三个方案的转化率数据(%) plan_a = np.random.normal(5.8, 1.2, 60) plan_b = np.random.normal(6.5, 2.0, 60) plan_c = np.random.normal(6.0, 1.5, 60) fig, ax = plt.subplots(figsize=(8, 6)) data = [plan_a, plan_b, plan_c] # patch_artist=True 让箱子填充颜色 bp = ax.boxplot(data, labels=["方案A", "方案B", "方案C"], patch_artist=True, showmeans=True) colors = ["#ED7D31", "#5B9BD5", "#70AD47"] for patch, color in zip(bp["boxes"], colors): patch.set_facecolor(color) patch.set_alpha(0.7) ax.set_title("不同运营方案的用户转化率分布", fontsize=15) ax.set_ylabel("转化率(%)") plt.show()逐段解释这个箱线图的读法:
- 每个箱子中间那根横线是中位数;如果它在箱子中间偏上或偏下,说明数据分布不对称。
- 箱子上下边缘是上下四分位数,箱子高度就是四分位距,反映中间50%的数据有多分散。
- 箱子外伸出的“须”一直延伸到非异常边界,再远一点的圆点就是异常值。
- 我在参数里加了
showmeans=True,箱子里的三角形标记就是均值。中位数和均值的位置关系,能很快告诉你数据是否有偏态。
方案B的箱子明显比方案A高,说明B的转化率总体上更高;但B的箱子也更长,说明B的稳定性稍差、波动更大。这种信息用柱状图根本看不出来,这就是箱线图不可替代的地方。
3.6 饼图与环形图:占比展示的正确姿势
饼图虽然经常被吐槽,但用于少数类别的占比展示时,直观度确实无可替代。我用环形图版本,它比传统饼图好在中心可以留白放重点文字,视觉上更清爽:
labels = ["国产剧", "综艺", "动画", "纪录片", "其他"] shares = [42, 28, 15, 10, 5] fig, ax = plt.subplots(figsize=(7, 7)) # 生成环形图:wedgeprops里设宽度 wedges, texts, autotexts = ax.pie( shares, labels=labels, autopct="%.1f%%", startangle=90, counterclock=False, wedgeprops={"width": 0.45, "edgecolor": "white"} ) ax.set_title("平台内容播放时长占比", fontsize=15) # 让每个占比标签稍微往外挪一点,避免贴字 for text in autotexts: text.set_fontsize(11) text.set_color("white") plt.show()控制为一个环形再在中心添加文字是常用做法:
ax.text(0, 0, "总计\n100%", ha="center", va="center", fontsize=13, fontweight="bold")注意饼图的几个雷区:
- 各部分之和必须是100%(或归一化成百分比)。
- 分类不要超过5个,“其他”兜底是好习惯。
- 如果有“确实占比极小”的分类,比如1%,画出来就是一条细线,读了等于没读,建议直接并入“其他”。
startangle=90是从12点钟方向开始排布,counterclock=False控制方向,两者结合可以让最大的扇区从顶部开始,符合阅读习惯。
4. 多图组合与细节增强:让图表从“能看”到“耐看”
4.1 子图布局:一张大图讲清一个完整故事
分析报告里,一张图讲一件事是理想状态,但有时候你需要把同一组数据的不同侧面放在一起对比。用subplots创建多行多列的子图网格,是很成熟的做法。比如把销量趋势、渠道占比和用户分布放在一张大画板上:
fig, axes = plt.subplots(2, 2, figsize=(14, 10)) # 左上:折线趋势 axes[0, 0].plot(months, sales, marker="o", color="#5B9BD5") axes[0, 0].set_title("月度销售趋势") # 右上:渠道占比柱状图 axes[0, 1].bar(["直营", "代理", "电商"], [260, 180, 320], color=["#ED7D31", "#A5A5A5", "#70AD47"]) axes[0, 1].set_title("渠道销量对比") # 左下:用户在线时长分布直方图 axes[1, 0].hist(session_minutes, bins=20, color="#4472C4", alpha=0.7) axes[1, 0].set_title("用户时长分布") # 右下:转化率箱线图 axes[1, 1].boxplot(data, labels=["A", "B", "C"]) axes[1, 1].set_title("转化率分布") fig.tight_layout() plt.show()axes[0, 0]这种索引方式对应2行2列的四个子图位置。每个子图都是独立的Axes对象,可以分别调用各自的绘图方法和设置函数。fig.tight_layout()特别重要,它自动调整子图之间的间距,避免标题和坐标轴重叠。共同标题可以用fig.suptitle()加在整个大画板上方。
4.2 双Y轴:两组不同量纲的数据画在一张图上
当你想对比销售额和利润率这两个量纲完全不同的指标时,双Y轴是实用方案。比如看“销售额涨了多少”和“利润率变化”是否同步:
fig, ax1 = plt.subplots(figsize=(10, 5)) x = np.arange(1, 13) sales_values = [120, 135, 148, 141, 160, 175, 189, 195, 210, 232, 248, 265] profit_rates = [12.1, 12.5, 11.8, 12.3, 12.9, 12.2, 11.5, 11.2, 10.8, 11.0, 10.5, 10.2] # 左轴画柱状图 ax1.bar(x, sales_values, color="#5B9BD5", alpha=0.7, label="销售额") ax1.set_xlabel("月份") ax1.set_ylabel("销售额(万元)", color="#5B9BD5") # 创建共享x轴的第二个坐标轴 ax2 = ax1.twinx() ax2.plot(x, profit_rates, color="#ED7D31", marker="o", linewidth=2, label="利润率") ax2.set_ylabel("利润率(%)", color="#ED7D31") plt.show()twinx()会创建一个和原轴共享x坐标、但y轴独立的新坐标轴。这样销售额柱状图用左轴刻度,利润率折线用右轴刻度,互不干扰。不过要提醒一句:双Y轴有时候会误导读者,如果两边的起始刻度范围差异过大,视觉上的“高低对比”可能会失真。所以除非两组数据量纲确实完全不同,否则优先考虑分两个子图并排展示,更稳妥。
4.3 seaborn一行代码提升图表档次
如果你的数据是规整的DataFrame,那seaborn在很多场景下能带来观赏性和信息量的双重提升。它的核心优势是对数据框友好——data=df加上列名就能干活。以散点趋势和箱线图为例:
import seaborn as sns # 保持中文字体设置 plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei"] plt.rcParams["axes.unicode_minus"] = False df = pd.DataFrame({ "广告投入": ad_spend, "销售额": sales }) fig, ax = plt.subplots(figsize=(9, 6)) sns.regplot(data=df, x="广告投入", y="销售额", scatter_kws={"alpha": 0.6, "s": 45}, line_kws={"color": "red", "linestyle": "--"}, ax=ax) ax.set_title("广告投入 vs 销售额(seaborn回归图)", fontsize=15) plt.show()regplot自动完成“散点+线性回归线+置信区间带”,在很多分析报告里,单靠这一张图就能替代“散点图+相关检验”两步工作。seaborn还有pairplot,对一个多列DataFrame一次性生成所有两两变量的散点图和分布图,数据探索阶段尤其好用:
tips = sns.load_dataset("tips") sns.pairplot(tips, hue="day", palette="Set2") plt.show()运行出来会是一张大型矩阵图,主对角线是每个变量的直方图,其他位置是两两散点图,并按day这个类别着色。五分钟之内你就能大概了解整个数据集的变量关系和分组差异,比逐个画图高效太多。
5. 常见问题排查与独家避坑技巧
5.1 中文乱码或负号显示方块
这是最常遇到的问题,原因和解决办法都很统一:matplotlib默认字体库不含中文字形,负号又用了特殊的Unicode字符。处理方法就是文章开头那两行rcParams设置。我汇总一下可能的情况:
| 现象 | 原因 | 解决方式 |
|---|---|---|
| 中文变方块 | 默认字体无中文 | 设置font.sans-serif为系统已有中文字体 |
| 负号变方块 | 负号编码问题 | rcParams["axes.unicode_minus"] = False |
| 图片保存后字体变乱 | 保存时未重设字体 | 保存前重新执行字体配置 |
| Linux服务器上无微软雅黑 | 系统未装中文字体 | 安装wqy-zenhei或Noto Sans CJK,或用matplotlib.font_manager指定字体路径 |
设置字体后如果还不行,可以用matplotlib.font_manager手动定位字体文件路径:
import matplotlib.font_manager as fm font_path = "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc" # 以实际路径为准 my_font = fm.FontProperties(fname=font_path) fig, ax = plt.subplots() ax.set_title("中文标题", fontproperties=my_font) plt.show()这种方式适合字体文件路径明确的场景,不会因为rcParams没生效而抓狂。
5.2 横坐标标签太密集
画折线或者柱状图时,如果x轴有几百个类别,默认设置会把所有标签叠在一起,完全没法看。我的经验是分情况处理:如果x轴本身就是时间序列,可以直接用plt.xticks(rotation=45)旋转角度;如果标签实在太多,就手动指定一部分刻度显示:
# 假设有100个日期 x_dates = pd.date_range("2024-01-01", periods=100, freq="D") values = np.random.randn(100).cumsum() fig, ax = plt.subplots(figsize=(12, 5)) ax.plot(x_dates, values) # 只显示第0、20、40、60、80天的刻度 ticks = x_dates[::20] ax.set_xticks(ticks) ax.set_xticklabels([d.strftime("%Y-%m-%d") for d in ticks], rotation=30) plt.show()x_dates[::20]是Python切片的步长取法,每隔20天取一个刻度,100天就只剩6个刻度。同时把日期格式化成2024-01-01的样子并旋转30度,基本不会再有文字重叠问题。如果你的分析目标是看长期趋势而非每一天的具体波动,还可以用pd.date_range的resample("ME")或resample("W")把数据聚合到月/周级别再画,效果更干净。
5.3 柱状图类别顺序不对或不按预期排序
pandas、matplotlib对类别顺序的处理规则不一样,导致同样的数据有时顺序错乱。如果你是直接传字符串列表给bar,顺序一般就是列表顺序;但如果你用df.plot(kind="bar"),x轴顺序则是DataFrame原有行顺序,排序后画图更容易表达“哪个最高”:
df_sorted = df.sort_values("销售额", ascending=False) fig, ax = plt.subplots(figsize=(9, 5)) ax.bar(df_sorted["月份"], df_sorted["销售额"], color="#70AD47") ax.set_title("按销售额降序排列") ax.tick_params(axis="x", rotation=45) plt.show()如果想固定自然顺序(比如月份1-12),用pd.Categorical指定categories即可。这种情况在使用seaborn的barplot时需要特别注意,因为它的order参数要显式传:
sns.barplot(data=df, x="月份", y="销售额", order=moths_order)5.4 数据点太多导致图糊成一团
当你有几万甚至几百万个数据点的时候,普通散点图会渲染成一片实色,看不出密度差异。这种情况我有两个处理方向:
- 一是用
alpha降低透明度,让重叠区域颜色更深,视觉上产生密度层次感。 - 二是改用
sns.kdeplot或hexbin绘制密度图。hexbin把平面划分成六边形格子,每个格子里的点数用颜色深浅表示,大数据量下效果远胜散点。
fig, ax = plt.subplots(figsize=(8, 6)) hb = ax.hexbin(ad_spend, sales, gridsize=30, cmap="YlOrRd") fig.colorbar(hb, ax=ax, label="数据点数量") ax.set_title("广告投入与销售额密度图(hexbin)") ax.set_xlabel("广告投入(万元)") ax.set_ylabel("销售额(万元)") plt.show()gridsize=30控制网格粗细,cmap="YlOrRd"控制颜色渐变。用上这个方案,即使10万量级的数据点也能在数秒内渲染出清晰的分布形态。
5.5 画图时使用现成代码模板,减少重复劳动
多画几次之后你会发现自己常用的就那么几种组合。我自己会维护一个plotting_templates.py文件,里面写好封装的函数,比如quick_line(df, x, y)、quick_bar(df, x, y, title)、quick_hist(series, bins)等。每次有新需求,直接调用然后微调参数,出图速度能快一倍以上。顺手写一个空的骨架模板给你:
def quick_line(df, x_col, y_col, title="趋势图"): fig, ax = plt.subplots(figsize=(10, 5)) ax.plot(df[x_col], df[y_col], marker="o", linewidth=2) ax.set_title(title, fontsize=15) ax.set_xlabel(x_col) ax.set_ylabel(y_col) ax.grid(True, linestyle="--", alpha=0.5) fig.tight_layout() return fig, ax封装之后,重复的东西不再重复,新手阶段可以先手写几遍理解原理,熟练之后再开始抽函数。
6. 最后再分享几个我踩过之后记住的经验
第一个关于数据准备。画图之前,务必先确认数据类型:时间列是不是datetime64,数值列是不是数值类型。我见过很多次df["月份"]是字符串类型,导致折线图的x轴顺序变成“1、10、11、12、2……”这种按字典序排序的乱序。最稳妥的做法是先运行df.info()或df.dtypes检查,数据类型不对就先用pd.to_datetime()或astype(float)转换。
第二个关于配色和可读性。默认的matplotlib配色表在板报和论文里都偏淡,但离“好读”仍有一定距离。没有设计基础的建议直接用seaborn的sns.set_theme()配色,或者参考颜色工具网站配一组主题色。每张图颜色最多4-5种,多一个维度就多一份脑力负担。分类超过6个的图,别硬用颜色区分,改用分组或面板图。
第三个关于交互式探索。静态matplotlib适合出报告,但你在做数据探索时,数据量一旦大起来,静态图看不过来会错过细节。推荐安装plotly,用一行plotly.express.scatter(df, x="广告投入", y="销售额", hover_data=["日期"])就能得到可缩放、可悬停查看详情的交互图。顺带一提,用Jupyter Notebook时,深度排查可以用%matplotlib widget把matplotlib自带的交互模式打开,缩放平移都很方便。
第四个关于机器学习模型的可视化。如果你除了统计图还做模型分析,建议把预测值、真实值、残差画成三张图放在一起对比:折线图看预测贴合程度、散点图看残差分布、直方图看残差是否集中在0附近。这套组合能快速判断模型是否存在系统性偏差,比看单一指标要直观很多。可视化本质上是探索数据、验证结论的外部工具,代码写得再顺,也不能离开“你想从数据里找什么”这个大前提。先把这个问题想清楚,再打开编辑器,你的图自然就有灵魂了。