Pandas 绘图这功能,用熟了你会发现它是整个数据处理链条里最被低估的一环。做数据分析的人天天跟 DataFrame 打交道,但真正到了出图环节,很多人第一反应还是打开 Matplotlib 文档抄样板代码。其实 Pandas 自带一套完整的绘图体系,df.plot() 一行就能覆盖绝大部分探索性需求。我这次直接把 12 类基础图从头到尾过一遍,从 line、bar、hist 这些日常高频图,到 scatter_matrix、parallel_coordinates 这种多变量视角,把 Pandas 里的原生绘图类型全给你捋清楚。不管你是刚入门 Pandas,还是做了几年分析想补全绘图技能树,这篇都可以当手册用。
1. Pandas 绘图值得学吗:先搞懂它的定位与 12 类图全景
1.1 为什么绘图逻辑对:少些样板代码,多些探索速度
Pandas 绘图本质上是把 Matplotlib 的绘图能力包了一层“数据友好”的外壳。DataFrame 和 Series 里的索引、列名、缺失值处理,在人类眼里是表格,在 Matplotlib 眼里是各种散装的数组,中间需要大量的坐标轴设置、图例设置、数据格式化。Pandas 把这层翻译工作直接吸收了,所以 df.plot() 才省事。
我自己判断什么时候用 Pandas 绘图,标准很简单:这张图是拿来做探索分析,还是要上会、进报告、做出版级印刷?前者一律 Pandas 绘图,后者才去精调 Matplotlib。因为探索性分析的核心诉求是“快”,你在三分钟内多画几张图,才能更快判断数据里到底有没有信号。如果每张图都从 Matplotlib 的 plt.figure() 开始撸,一顿操作下来思路都被打断了。
这里也解释一下 Pandas 绘图和 Matplotlib 的关系,不是替代而是互补。Pandas 的图后端就是 Matplotlib,两者可以混用,返回的 Axes 对象可以直接被 Matplotlib 继续修饰。这种机制决定了它不可能把所有排版功能都暴露出来,但对大多数快速看图场景已经足够了。
1.2 12 类基础图全景:哪些是原生 kind,哪些由 plotting 模块补齐
先给一张全景表,方便你对应到代码里的写法。我这里说的 12 类,是 10 种 DataFrame.plot 的 kind 参数图,加上 pandas.plotting 模块里的两个高频多变量图,这样基本覆盖了 Pandas 暴露给用户的全部原生绘图入口。
| 图类型 | 调用写法 | 典型场景 |
|---|---|---|
| line 线图 | df.plot(kind='line') | 趋势变化、时间序列 |
| bar 柱状图 | df.plot(kind='bar') | 分类别数值对比 |
| barh 水平柱状图 | df.plot(kind='barh') | 类别名较长时的对比 |
| hist 直方图 | df.plot(kind='hist') | 单变量分布形态 |
| box 箱线图 | df.plot(kind='box') | 分位数、离群值体检 |
| area 面积图 | df.plot(kind='area') | 多序列堆叠占比与总量 |
| scatter 散点图 | df.plot(kind='scatter', x=..., y=...) | 两变量相关关系 |
| pie 饼图 | df.plot(kind='pie') | 组成占比(类别少时) |
| hexbin 六边形图 | df.plot(kind='hexbin', x=..., y=...) | 大数据量散点密度 |
| kde 核密度图 | df.plot(kind='kde') | 平滑分布估计 |
| scatter_matrix 散点矩阵 | pandas.plotting.scatter_matrix(df) | 多变量两两关系 |
| parallel_coordinates 平行坐标 | pandas.plotting.parallel_coordinates(df, '类别列') | 高维样本聚类/分类模式 |
这个表里的 12 类,我后文会一个一个讲清楚,包括参数、注意点和使用场景。先记住一个结论:Pandas 绘图擅长的是“数据探索期快速成图”,而不是“出版级排版”。后面遇到的具体坑,我在第 3 章逐节展开。
1.3 import 方式与版本:为什么有人写 df.plot 直接报错
有个细节很容易被忽略:新版 Pandas 中,df.plot 是 DataFrame 的内置方法,只要 import pandas 就能用。但如果你的环境里同时装了老版本 Pandas 或曾经改过 matplotlib 后端,可能会遇到 “module 'pandas' has no attribute 'plot'” 之类的问题。这个问题的常见原因,是你在没有正常导入 pandas 的情况下,调用了 pd.plot 这种容易混淆的写法。
正常情况下用 import pandas as pd,然后 df.plot() 就够了。如果图不弹出来,多半是当前 notebook 后端没选好,补充一行 %matplotlib inline(Jupyter 环境)或者 plt.show()(脚本环境)就能解决。还有一类情况是 Pandas 老版本代码里 kind='kde' 的写法现在依然兼容,但升级版本时注意看看 deprecation warning。遇到这类报错,我不建议靠记忆硬扛,直接看 warning 信息里提示的替代写法,比上网查更快。Pandas 中文文档和官方 API 参考里对这些变更记录得很清楚,画图之前先花十分钟把 df.plot? 的帮助信息拉出来扫一眼,能少踩一半坑。
2. 画图前先处理三件事:数据结构、字体和画布参数
2.1 Series 和 DataFrame:不同结构决定不同出图方式
Pandas 绘图的最小单位是 Series 和 DataFrame。Series.plot() 画出来的是单个序列的图,DataFrame.plot() 默认把每一列当成一个序列画到同一张图上,列名自动变成图例。这个区别看似简单,但很多人第一次画图就翻车:比如想画两列散点,却直接 df.plot(),出来的不是散点而是两条折线。
所以动手前先想清楚,你的数据是一个序列还是多个序列,以及目标是什么。如果是两列之间的关系,scatter、hexbin 这种图必须明确指定 x 和 y 参数。另外,Series 和 DataFrame 的索引会成为默认的 x 轴,所以索引的干净程度直接影响出图质量。常见的数据清洗工作——去重、排序、类型转换——本质上也是在为绘图铺路。比如时间列如果是字符串,画线图之前最好先 pd.to_datetime 转成时间类型,否则 x 轴顺序会按字典序排,容易画出一条乱乱的折线。
补充一个我常用的检查:df.dtypes 一定要跑一遍。如果某列显示 object 而你想画数值图,先 pd.to_numeric 或者 astype 处理掉。这是“pandas 数据类型转换”里最常被问到的绘图前置问题,比画图本身更值得花时间。数据清洗和画图不是两件事,脏数据喂进去,出来的图一样是脏的,后面第 5 章我会专门展开。
2.2 中文字体和负号显示:不配置就一团乱码
这个坑我踩了不下三次。第一次画图,标题、图例里的中文全是方块,还以为数据有问题,结果折腾半天是字体缺失。Pandas 绘图底层是 Matplotlib,Matplotlib 默认字体不包含中文字符,所以必须手动指定一个支持中文的字体。常见的配置方式是这样:
import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] # 或者其他中文字体 plt.rcParams['axes.unicode_minus'] = False # 解决负号显示成方块的问题第一行指定中文字体,第二行尤其容易被忽略:如果 axes.unicode_minus 保持默认 True,坐标轴的负号在部分字体渲染下会显示成一堆小方块,看起来非常业余。不同系统的字体名不一样,Windows 常用 SimHei / Microsoft YaHei,macOS 可以用 PingFang SC 或 Arial Unicode MS,Linux 可以装文泉驿或 Noto Sans CJK。配置之后如果还是乱码,可以先用下面这段代码查一下当前环境中到底有哪些可用字体:
import matplotlib.font_manager as fm fonts = [f.name for f in fm.fontManager.ttflist if 'CJK' in f.name or 'Hei' in f.name or 'Song' in f.name] print(set(fonts))这段输出会告诉你该填哪个字体名。注意字体名和字体文件路径是两回事,我试过直接把 .ttf 路径填进 rcParams,结果完全不生效。
2.3 figsize、subplots 和 layout:先规划好画布再动手
画布尺寸这种事,晚调不如早调。如果你一开始就用默认尺寸画多列数据,后面再改 figsize,往往要重新调整字体、图例位置,返工成本很高。Pandas 绘图里 figsize 可以直接传进 plot 方法:
df.plot(kind='line', figsize=(10, 5))另一个高频参数是 subplots。当 DataFrame 有多列,而且你想把它们拆成上下多个小图时,subplots=True 非常省事:
df.plot(kind='line', subplots=True, layout=(3, 1), figsize=(8, 10))layout 控制几行几列,sharex 和 sharey 可以控制子图之间是否共享坐标轴。这里我建议一上来就把 figsize 按内容量估准,宁可偏大不要偏小,后面做标注、加注释时空间才够用。画多图对比时,把相关的序列放在同一个子图里比拆成多个子图更适合看相关性,这个后面第 4 章还会继续讲。
3. 12 类基础图逐个拆解
3.1 line 线图:默认图表,先看数据点玩的是趋势
line 图是 Pandas 绘图的默认类型,df.plot() 不带任何 kind 参数时画的就是它。数据里只要是带顺序的索引(时间、步数、排名),我都会先画一张 line 看整体走势。最简单的用法:
import pandas as pd import numpy as np np.random.seed(42) idx = pd.date_range('2024-01-01', periods=100, freq='D') df = pd.DataFrame({'销量': np.random.randint(50, 200, size=100)}, index=idx) df.plot(figsize=(10, 4))默认情况下,DataFrame 的索引会成为 x 轴,列名成为图例。这个行为很省心,但也容易埋坑:如果索引没有排序,画出来的线会来回折返,像一团乱麻。尤其是做时间序列时,我总会先 df.sort_index() 再画。另一个常用参数是 marker,想强调数据点时可以 df.plot(marker='o'),但数据点非常多时建议去掉 marker,否则图会显得很脏。
如果 DataFrame 有多列,默认会把所有列画到同一张图上,而且 y 轴量纲不同时会互相压缩。比如一列是销量(几十到几百),一列是单价(几块钱),两者画在一起,销量会把单价压成一条近似水平的直线。这时候我的经验是:要么先标准化,要么 subplots=True 拆开画。
注意:line 图对缺失值很敏感。如果序列里有 NaN,Matplotlib 默认会跳过该点,线会断掉。要保留连接线,可以先 df.interpolate() 补上再画,否则出图的连续性会误导判断。
3.2 bar 柱状图:分类别数值对比
bar 图适合表达“类别到数值”的对比。在 Pandas 里,bar 的 x 轴来自 DataFrame 的索引,所以通常先把要对比的类别放到索引上:
sales = pd.Series({'华东': 120, '华南': 90, '华北': 110, '西南': 70}) sales.plot(kind='bar', figsize=(8, 4))如果传入的是 DataFrame,每一列会变成一组柱子,图例会按列名展示。想改成横向对比,用 barh 就行。barh 在类别名很长时特别友好,比如省份名称、产品型号,横着放标签不会被截断。
柱状图有两个高频参数值得留意:stacked 和 color。stacked=True 可以把多列堆叠起来,看的是总量加构成;color 传入一个列表可以直接指定每列颜色。由于 Pandas 默认按列名自动分配颜色,如果你对色板有要求,建议提前定义好颜色列表,而不是画完再一根根改。
实操里我发现 bar 和 barh 的顺序问题比想象中常见。默认柱子顺序跟索引顺序一致,如果索引是字符串,会按字典序排列,往往不是你期望的业务顺序。解决办法是用 pd.Categorical 或者 reindex 手动排好序。这一步在画图之前做,比画完再用 Matplotlib 的 tick 调整要干净得多。
3.3 hist 直方图:用 bins 控制分箱粒度
hist 是单变量分布的一线侦察兵。一句话说清原理:把数值范围切成若干区间,然后数每个区间里有多少样本,柱子的高度就是这个区间内的样本数。bins 参数决定切多少份,这个数直接决定图的信息粒度。
df['销量'].plot(kind='hist', bins=20, figsize=(8, 4))bins 太小时,分布的形状会被抹平,看不出峰和谷;bins 太大时,噪音会被放大,依然呈现锯齿状。我的经验是,先试 bins=30 看整体,再根据数据量调整。数据量几千条时 20-50 都合理,几万条以上可以尝试更多分箱。
DataFrame 多列也可以一起画 hist,默认会重叠在一起,容易看不清。这时用 subplots=True 拆成多个小图,或者给每列设置 alpha=0.5 半透明叠加。直方图很容易让人误判双峰分布,其实可能是两个不同来源的数据混在一起。遇到这种形态,我会先把数据按某个分组维度分开再画一次,往往会有新发现。
3.4 box 箱线图:一组数字的体检报告
box 图会一次性展示中位数、四分位数、四分位距和离群点,相当于给数值列做体检。你的数据里如果存在明显的离群点,通过 box 一眼就能定位到是哪几条记录把分布拖偏了。
df[['销量', '单价', '库存']].plot(kind='box', figsize=(8, 5))如果 DataFrame 有多个数值列,box 会把每一列当成一个箱子横着排出来,方便比较不同列的分布。还有一个好用的是 by 参数,能按某个分类列把箱线图分组:
df.plot(kind='box', by='区域', figsize=(10, 5))这个写法会把每个分类下的分布并排展示,非常适合“不同区域的销量差异”这类问题。我拿到新数据做体检时,通常会先画一个全数值列的 box,看到哪个箱子的离群点特别多,再去翻原始数据,效率比肉眼扫表高很多。
注意:box 图对异常值的容忍度很高,但这不代表你应该忽略它们。画完 box 后,可以再用 df[df['销量'] > 上界] 把离群点挑出来看,通常能发现数据采集错误或业务上的特殊事件。
3.5 area 面积图:堆叠趋势一眼看全
area 图可以理解为“填了颜色的 line 图”,默认还会把多条序列堆叠起来。堆叠后不但能看到每条序列自己的趋势,还能看到总量和占比的演化,适合看三个月内各渠道的流量贡献:
df[['A渠道', 'B渠道', 'C渠道']].plot(kind='area', figsize=(10, 5))Pandas 的 area 图默认 stacked=True,也就是堆叠模式。如果多条序列量级差异太大,堆叠后的小序列几乎会被大序列压扁,这时要么分开画,要么先做归一化。另一个容易踩的坑是 area 图对索引顺序要求高,索引乱序会造成填充区域乱交叉。所以画 area 前,我会先确认索引有序并且类型正确。
我自己的习惯是:看总量变化用堆叠 area,想看各序列自身趋势时关闭堆叠(stacked=False),再加一点透明度。切记不要把颜色配得太深,否则多个区域叠在一起会变成一团黑,信息反而丢了。
3.6 scatter 散点图:两变量相关性的第一印象
scatter 图是探索两列关系的默认答案。它必须显式指定 x 和 y:
df.plot(kind='scatter', x='广告投入', y='销量', figsize=(8, 6))散点图最大的价值是让你在算相关系数之前,先肉眼看清楚两个变量的关系形态。线性相关、非线性、异方差性、甚至离群点,在散点图上一目了然。如果点非常多,可以用 alpha 参数降低透明度,避免一个黑坨;也可以用 c 参数按第三列着色,比如点的颜色代表另一个维度,这样散点图就从二维关系扩容成三维甚至四维信息。
这里我要强调一下:Pandas 散点图的 x 和 y 参数是必须的。如果你直接 df.plot(kind='scatter'),大部分情况下会直接报错,因为 Pandas 不知道拿哪两列去画。这是新手最常见的一个报错场景,后面第 5 章我整理了一张速查表,可以直接对着排查。
3.7 pie 饼图:能用但别滥用
pie 在 Pandas 里的写法很简单,但限制也最明显:它通常只接受 Series,不接受 DataFrame 太多列。Pandas 会按索引作为扇区标签,按值作为占比。用法如下:
sales = pd.Series({'华东': 120, '华南': 90, '华北': 110}) sales.plot(kind='pie', autopct='%.1f%%', figsize=(6, 6))如果你把 DataFrame 直接传给 kind='pie',Pandas 会尝试为每一列画一张饼图,大概率不是你想要的结果。而且饼图的扇区太多时很难看,超过 5 到 7 个类别,我建议大家换 bar 或者 barh。饼图的比较精度也很差,人类对角度的感知远不如对长度的感知,这也是我日常几乎只用它做“整体构成展示”的原因。
3.8 hexbin 六边形图:大数据量散点的救星
当散点多到几万、几十万时,scatter 画出来的基本是一团黑,完全看不到密度分布。hexbin 把二维平面切成很多小六边形,统计每个格子里的样本数,再用颜色深浅表示密度,相当于散点图的“密度热点图”:
df.plot(kind='hexbin', x='x列', y='y列', gridsize=20, figsize=(8, 6))gridsize 控制六边形网格的密度,越小颜色块越粗,越大越细腻。实际使用中,几十万行数据用 hexbin 画起来,流畅度和清晰度都远胜 scatter。如果你发现 hexbin 图例上的颜色条默认位置不顺手,可以用 Matplotlib 的 colorbar 方法调整,这类细节可以放在第 4 章统一处理。
注意:hexbin 需要的数据格式是两列数值型数据,如果有缺失值,Pandas 会自动跳过包含 NaN 的行,这是它底层实现的行为。你不需要手动 dropna,但前提是缺失值占比不能太高,否则图会失去代表性。
3.9 kde 核密度图:比直方图更平滑的分布线
kde(也叫 density)图可以看作直方图的“平滑连续版”。它不是把数据切成箱子,而是用核函数给每个样本周围铺一条“概率密度山包”,叠加起来形成一条平滑的密度曲线:
df['销量'].plot(kind='kde', figsize=(8, 4))当你想比较两组数据分布的形状差异时,kde 比 hist 更直观,因为多条密度曲线可以清晰地叠在一起而不互相遮挡。bw_method 参数控制曲线的平滑程度,值越小曲线越贴近原始数据,越大越平滑。我的经验是先用默认值,如果曲线锯齿感强就适度调大;如果被抹平到看不出峰,就调小。
kde 和 hist 不是替代关系。我会先用 hist 看原始柱状形态,再用 kde 确认平滑后的趋势。如果两个峰在 hist 中清晰可见,kde 中却变成一个缓坡,那很可能是样本量太少或者带宽设置偏大,需要回退检查数据量和参数。
3.10 scatter_matrix 散点矩阵:多变量两两关系一眼扫完
scatter_matrix 属于 pandas.plotting 模块,不是 DataFrame.plot 的 kind,但画起来同样是一行代码:
from pandas.plotting import scatter_matrix scatter_matrix(df[['销量', '单价', '库存']], alpha=0.5, figsize=(10, 10), diagonal='hist')它的输出是一个矩阵:对角线位置画每个变量的分布(默认是直方图,也可以改成核密度估计或箱线图),非对角线位置画两两变量的散点图。这个图在做数据探索初期非常高效,一口气能看得出哪些变量之间可能有线性关系、哪些变量是偏态分布、哪些组合有明显离群点。
数据列数多时,我不会把所有列都丢进去,一般选 4 到 6 个核心变量,否则矩阵会大到看不清。diagonal 参数是我最常用的:diagonal='hist' 让对角线看分布,diagonal='kde' 看平滑密度。scatter_matrix 返回的是一个 Axes 的二维数组,后面想做局部调整可以直接按索引定位。
3.11 parallel_coordinates 平行坐标:高维数据的全貌视图
平行坐标适合看“高维样本在类别之间的分离模式”。每个样本用一条折线串过所有特征,颜色按分类列标记。如果某个类别在某一列上明显偏移,折线束会呈现“分叉”形态,特征列的作用就一下看出来了:
from pandas.plotting import parallel_coordinates parallel_coordinates(df[['销量', '单价', '库存', '区域']], class_column='区域', colormap='Set1', figsize=(10, 5))注意这里必须指定 class_column,也就是用来分组的类别列,否则函数不知道按什么着色。平行坐标对特征量纲很敏感,如果一列动辄上千、另一列只有个位数,小量纲列会被压平。我的习惯是先用 sklearn 的 StandardScaler 或者 Pandas 的 (df - df.mean()) / df.std() 做标准化,再看类别分叉。
3.12 12 类之外的补充:绘图函数里还藏着几个彩蛋
到这里,12 类基础图已经全部过了一遍:前 10 种是 DataFrame.plot 的 kind,后 2 种是 pandas.plotting 的高频函数。如果你还想往深处挖,pandas.plotting 里其实还藏着 radviz、lag_plot、autocorrelation_plot、bootstrap_plot 这类不那么常用但偶尔能救命的图。lag_plot 看时间序列的自相关趋势,autocorrelation_plot 直接给出各滞后阶数的自相关系数,bootstrap_plot 可以快速评估统计量的稳定性。
这些图我平时用得少,但遇到时间序列数据时,autocorrelation_plot 是判断“这列数据到底有没有周期惯性”的最快手段。写法同样是一行:
from pandas.plotting import autocorrelation_plot autocorrelation_plot(df['销量'])所以 12 类基础图只是 Pandas 绘图能力的一个骨架,骨架之上还藏着一层扩展。第 5 章我会专门讲怎么灵活组合它们。
4. 样式控制与导出:从能画到画得能看
4.1 图例、颜色和网格:三个最常调的细节
Pandas 绘图的默认样式偏“裸奔”,离能见人的图还差几步。我每次画完图,第一件事就是看三样:图例位置、颜色搭配、网格线。图例默认位置由 Matplotlib 决定,经常挡住数据线。用 legend=False 关掉 Pandas 自带的图例,再手动调用 ax.legend(loc='best' 或 'upper left') 来控制。颜色建议在调色板里挑一组视觉友好的,不要用默认循环色硬凑。网格线用 ax.grid(True, linestyle='--', alpha=0.6)。
这里有一个技巧:所有 Pandas 图方法都返回 Axes 对象,你可以把返回值存下来再继续修饰:
ax = df.plot(kind='bar', title='各区域销量对比') ax.set_xlabel('区域') ax.set_ylabel('销量') ax.grid(True, linestyle='--', alpha=0.6)这样既保住了 Pandas 的省事,又拿到了 Matplotlib 的灵活性。
4.2 subplots 布局与共享坐标轴
当你要画多张图并排对比时,可以灵活使用 subplots、layout 和 sharex。subplots=True 会把 DataFrame 的每一列画到独立的小图,layout=(2,2) 控制几行几列。sharex=True 和 sharey=True 可以让坐标轴对齐,方便比较不同子图的趋势和量级。
不过我的实际经验是,多序列对比时,把不同序列画在同一个子图里往往更容易看出相对关系;子图更适合量纲相差过大或者类别语义不同的列。比如历史销量和当天销量可以放一张图,销量和库存就应该拆开,因为它们单位不同、业务含义也不同。
4.3 保存图片:dpi、bbox_inches 和 format
Pandas 绘图本身不直接提供 savefig 参数,但返回的 Axes 可以拿到对应的 Figure:
import matplotlib.pyplot as plt ax = df.plot(kind='line', figsize=(10, 5)) fig = ax.get_figure() fig.savefig('sales_trend.png', dpi=300, bbox_inches='tight')dpi=300 是报告和展示的基本要求,bbox_inches='tight' 可以自动裁掉多余白边。这里还有个容易踩的坑:保存前最好调用 plt.tight_layout(),否则标题可能被截掉。有的朋友会直接用 plt.savefig,也能用,但我更推荐 ax.get_figure().savefig,这样明确保存的是当前这张图,不容易把之前 notebook 里的其他图也一起覆盖。
5. 常见问题与排查技巧实录
5.1 一张表搞定最常见的 8 个报错和异常
| 问题表现 | 原因 | 解决方案 |
|---|---|---|
| kind='scatter' 报错 | 没传 x 和 y | df.plot(kind='scatter', x='列A', y='列B') |
| kind='pie' 报错或图太乱 | 传入了 DataFrame 多列 | 改成 Series.plot(kind='pie') |
| 中文全是方块 | 中文字体未配置 | 配置 plt.rcParams['font.sans-serif'] |
| 负号全是方块 | axes.unicode_minus 默认 | 设置 plt.rcParams['axes.unicode_minus'] = False |
| 图不显示 | 非 notebook 环境没调用 plt.show() | 脚本末尾加 plt.show() |
| hexbin 图空白 | x/y 列有大量 NaN | 先 dropna 或确认数据量 |
| line 图折线乱飞 | 索引没有排序 | df.sort_index() 后再画 |
| 多列 y 轴互相挤压 | 量纲差异太大 | subplots=True 拆图或先标准化 |
这些报错我自己基本都踩过一遍,每个都是几分钟可以解决的问题,但第一次碰上确实容易愣住。把这张表保存在手边,比临时翻报错堆栈效率高很多。
5.2 数据清洗与绘图的关系:脏数据画出来的图也脏
很多人把数据清洗和绘图分开看待,其实两者密不可分。一个典型场景:销量列是字符串 “1,234”,Pandas 会把它识别成 object,画出来的图要么报错要么全是乱码。这时候必须先做类型转换:
df['销量'] = pd.to_numeric(df['销量'].str.replace(',', ''), errors='coerce')还有缺失值。画 box 前你不 dropna 也不一定报错,但图里的统计结果会受影响。重复行在聚合类图表里会造成虚高,画柱状图前最好先 df.drop_duplicates()。这些事不是绘图方法本身能解决的,必须在进入画图环节之前处理好。我写这份手册时,特别在第 2 章强调数据结构,就是因为画图翻车的原因大概率都在数据侧,而不是 Pandas 侧。
5.3 大数据量绘图卡顿:先用采样和分箱
几十万行的 DataFrame 画散点或者线图,即便能出图也会卡到想砸电脑。我自己的处理策略有三个:散点图改用 hexbin,速度快且信息密度高;线图按时间降采样,比如从逐小时变成逐日,用 resample('D').mean() 聚合一下,趋势不会丢,点却少很多;如果不适合聚合,可以用 df.sample(n=50000, random_state=42) 随机抽样后画图,先看个大概。
还有一个很多人忽略的点:图形加载慢不全是数据行数的问题,也可能是字体加载和环境渲染的问题。在 Web 端 notebook 里画大图尤其明显。遇到卡顿别硬等,先去数据量上想办法,效果立刻不同。
5.4 直接调 Matplotlib:什么时候 Pandas 绘图满足不了你
Pandas 绘图给你的是“快速达成”的路径,但它不会给你完整的出版级控制力。需要用到复杂多子图自由布局、坐标轴刻度精细定制、自定义图例形状、动画或交互式图表时,还是得直接用 Matplotlib 或更上层的库(比如 plotly、seaborn)。我的经验是,先想清楚这张图的核心目的是探索还是交付,再决定用哪一层工具。
比如 Seaborn,本质上是另一个封装 Matplotlib 的库,在统计绘图的默认样式和高级聚合上做得比 Pandas 更细。但 Pandas 的好处是你不用额外处理 DataFrame 格式,直接 df.plot() 就能跑通。所以我在实际项目里的流程通常是:数据整理,Pandas plot 快速出草图,有必要的图再换 Seaborn 或 Matplotlib 精修。这套流程能帮你省下至少一半的返工时间。
最后再分享一个我自己很受用的小技巧:Pandas 绘图所有方法都返回 Axes 对象,这意味着你不必在“Pandas 绘图”和“Matplotlib 精修”之间二选一。我通常先用 df.plot() 快速出图确认方向和结论,再用返回的 ax 对象去调 xlabel、ylabel、legend、grid 这些细节;只有特别重要的图才整体迁移到 Seaborn 或 Matplotlib。12 类图你不要想着一天全背下来,先用 line、bar、hist、scatter 这四类把日常问题覆盖住,用到其他图时再回翻这份手册。等哪天真碰到几万行以上的散点图,你会感谢自己当初多看了一眼 hexbin。