资讯动态

pandas与seaborn数据可视化:从数据整理到图形表达的完整工作流

发布时间:2026/10/13 14:17:38 来源:尧图企业网站定制
1. 为什么我劝你先别急着调样式很多人拿到一份数据第一反应是打开绘图库调颜色、改字体、加标题折腾半天发现图还是没法看。问题不在审美在于数据本身没整理干净。pandas 和 seaborn 这套组合之所以在数据分析圈里经久不衰核心原因就是它把数据整理和图形表达这两件事串成了一条流水线——pandas 负责把数据揉成绘图需要的形状seaborn 负责在这个形状上直接出图。我见过太多人把这两个库割裂开用用 pandas 读数据然后手动构造列表传给 matplotlib再套一层 seaborn 的样式。这种写法不是不行但等于放弃了 seaborn 最大的优势——它天生就是为 DataFrame 设计的。seaborn 的绝大多数函数都接受data参数后面直接跟列名这种长格式数据才是它的主场。这篇文章适合两类人一类是刚接触数据分析、想搞清楚从原始表格到一张能看的图完整链路的新手另一类是用过 matplotlib 但觉得代码又长又难维护、想换一套更高效工作流的老手。我会从数据准备讲到图形选择再到多图组合和常见坑尽量把每一步为什么这么做讲透而不是甩一堆参数让你背。先说一个反直觉的结论画图代码写得好不好八成取决于画图之前你对数据的理解够不够。seaborn 的 API 设计哲学就是让数据自己说话——你告诉它 x 是什么、y 是什么、按什么分组它自动帮你算聚合、画置信区间、分配颜色。但前提是你的数据得是它认识的样子。2. 绘图前的数据整形长格式与宽格式的取舍2.1 什么是长格式为什么 seaborn 偏爱它宽格式wide format是我们最熟悉的表格样子每一行是一个观测对象每一列是一个变量。比如一张成绩表列是语文、数学、英语行是学生。这种格式人看着舒服但 seaborn 画分组图时很别扭——它需要知道哪一列是数值、哪一列是分组标签宽格式里分组信息藏在列名里机器读不出来。长格式long format则把变量名也变成一列数据。同样一张成绩表长格式会变成三列学生、科目、分数。每个学生出现三次每次对应一个科目。看起来冗余但对绘图来说极其友好——你只要说x科目, y分数seaborn 立刻明白该怎么分组。用 pandas 的melt做这个转换import pandas as pd # 假设 df_wide 是宽格式成绩表 df_long df_wide.melt( id_vars[学生], value_vars[语文, 数学, 英语], var_name科目, value_name分数 )id_vars是保持不变的标识列value_vars是要融化成值的列var_name和value_name分别给新生成的两列起名。转换完之后df_long就是 seaborn 最喜欢的形状。提示如果你拿到的是长格式想变回宽格式用pivot_table或pivot但绘图场景下基本不需要反向操作。2.2 缺失值和异常值画图前必须处理的两件事seaborn 对缺失值的处理策略是默默丢掉这很危险。如果你的数据里有大量 NaN画出来的图会缺一块但你未必能第一时间发现。我的习惯是在绘图前先跑一遍df.isnull().sum() df.describe()第一行看每列缺多少第二行看数值分布有没有离谱的极值。对于缺失值如果占比很小比如低于 5%直接dropna可以接受如果占比大就得考虑填充或者单独分析缺失模式——因为缺失本身可能就是一种信息。异常值更麻烦。seaborn 的箱线图会自动把超过 1.5 倍四分位距的点标成离群点但散点图不会。如果你直接画散点一个极端值就能把整个坐标系拉扁其他点挤成一团。处理办法有两种一是绘图前用分位数截断二是用plt.xlim()手动限制坐标范围。我一般倾向后者因为截断数据会改变统计结果而限制显示范围只是视觉调整。# 查看分位数判断合理范围 print(df[数值列].quantile([0.01, 0.25, 0.5, 0.75, 0.99]))2.3 数据类型陷阱类别列必须是 category 吗seaborn 对字符串列和 category 列的处理略有不同。字符串列在绘图时会按字母顺序排列而 category 列可以自定义顺序。如果你想让低、中、高这样的有序类别按逻辑顺序而不是拼音顺序排列就得显式转换from pandas.api.types import CategoricalDtype cat_type CategoricalDtype(categories[低, 中, 高], orderedTrue) df[等级] df[等级].astype(cat_type)这一步不做图也能出来但 x 轴顺序可能是中、低、高读起来很别扭。这是很多人画完图觉得哪里不对却找不到原因的典型情况。3. 从单变量到多变量图形选择的决策路径3.1 单变量分布直方图、密度图还是箱线图看一个变量的分布最常用的是直方图。seaborn 的histplot比 matplotlib 的hist多了两个实用功能自动选择分箱数量binsauto和核密度曲线叠加kdeTrue。import seaborn as sns import matplotlib.pyplot as plt sns.histplot(datadf, x分数, binsauto, kdeTrue) plt.show()分箱数量对直方图的影响极大。分太少分布细节丢失分太多噪声被放大。binsauto用的是基于数据量和方差的经验规则大多数情况下够用。如果你要精细控制可以传整数。密度图kdeplot适合看连续变量的平滑分布尤其是样本量不大、直方图锯齿明显的时候。但要注意KDE 在数据边界附近会有溢出问题——比如分数不可能为负但密度曲线可能延伸到负数区域。这是数学性质决定的不是 bug但汇报时要心里有数。箱线图boxplot的优势是能同时展示中位数、四分位数和离群点适合多组对比。缺点是看不出双峰分布——两个峰如果中位数接近箱线图会显示成一个正常的箱子。所以我的习惯是先用直方图或密度图看单组分布形状确认没有诡异的多峰再用箱线图做多组对比。3.2 双变量关系散点图、折线图和回归图的分工两个连续变量的关系首选散点图scatterplot。seaborn 的散点图可以额外传hue颜色分组、size点大小映射、style点形状映射一张图最多能表达五个维度。sns.scatterplot(datadf, x学习时长, y分数, hue班级, size作业完成率)但散点图有个硬伤数据点太多时会重叠成一片看不出密度。解决办法是降低点的不透明度alpha0.5或者改用kdeplot的二维版本fillTrue画等高线填充。折线图lineplot适合时间序列或有序变量。seaborn 的lineplot默认会对同一 x 值的多个 y 做聚合均值并画置信区间这个行为在画原始数据时可能不是你想要的。如果每个 x 只有一个点没问题如果有多个点且你想全部画出来加estimatorNone。回归图regplot或lmplot在散点基础上加一条拟合线和置信带。regplot是轴级函数lmplot是图级函数支持分面。我一般用regplot做快速探索用lmplot做分面展示。3.3 多变量分面FacetGrid 的三种用法当变量超过三个颜色和形状不够用了就得上分面facet。seaborn 提供两个层级的分面工具FacetGrid是底层通用工具relplot、displot、catplot是封装好的图级函数。FacetGrid的基本用法g sns.FacetGrid(df, col班级, row性别, hue是否及格) g.map(sns.scatterplot, 学习时长, 分数) g.add_legend()col按列分面row按行分面hue在每张子图内再分颜色。这种网格颜色的组合能表达四到五个变量基本覆盖大多数分析场景。封装函数更省事比如relplotsns.relplot(datadf, x学习时长, y分数, col班级, kindscatter)kind参数可以切换scatter或line不用记两套 API。我的建议是快速探索用封装函数需要精细控制子图样式时用 FacetGrid。4. 样式控制让图从能看到能发4.1 主题与调色板两行代码改变整体气质seaborn 内置了五套主题darkgrid、whitegrid、dark、white、ticks。默认是darkgrid适合屏幕查看如果要打印或放进文档whitegrid或ticks更干净。sns.set_theme(stylewhitegrid, palettemuted, font_scale1.2)palette控制颜色方案。分类变量用deep、muted、pastel这类定性调色板连续变量用viridis、rocket、mako这类渐变调色板。font_scale全局放大字号省得一个个调。这里有个经验不要用彩虹色rainbow/jet表示连续变量。彩虹色在感知上不是均匀的中间亮两端暗会让人误以为中间值更重要。用viridis或rocket这种感知均匀的调色板数据高低和颜色深浅的对应关系更准确。4.2 坐标轴与图例那些默认设置不够用的地方seaborn 的默认坐标轴标签就是列名很多时候不够清楚。比如列名叫score图上就显示score但你想显示考试分数。两种改法# 方法一画图时直接指定 sns.histplot(datadf, xscore) plt.xlabel(考试分数) plt.ylabel(频数) # 方法二改数据列名不推荐会污染原始数据图例位置默认是最佳位置但最佳是算法判断的不一定符合你的预期。用plt.legend(locupper right)手动指定bbox_to_anchor可以精确控制位置。还有一个高频需求旋转 x 轴标签。当类别名很长时标签会重叠。加一行plt.xticks(rotation45, haright)haright让标签右对齐旋转后不会偏离刻度位置。4.3 保存图片分辨率、尺寸和格式的坑plt.savefig有三个参数必须显式设置dpi、bbox_inches、facecolor。plt.savefig(output.png, dpi300, bbox_inchestight, facecolorwhite)dpi300是印刷级分辨率屏幕展示 150 就够。bbox_inchestight自动裁掉多余白边否则默认会留一大圈空白。facecolorwhite是因为有些主题背景是透明的保存成 PNG 后在某些文档里会变成黑色。格式选择PNG 适合屏幕和文档SVG 或 PDF 适合需要后期编辑的场合矢量格式放大不失真。如果图要放进论文或报告优先 SVG。注意savefig必须在plt.show()之前调用。show()之后画布会被清空再保存就是一张白图。这个坑我踩过不止一次。5. 多图组合与布局管理5.1 subplots 与 seaborn 的配合方式seaborn 的轴级函数histplot、scatterplot、boxplot等都接受ax参数可以画在指定的子图上。这是组合多图的标准做法fig, axes plt.subplots(2, 2, figsize(12, 10)) sns.histplot(datadf, x分数, axaxes[0, 0]) sns.scatterplot(datadf, x学习时长, y分数, axaxes[0, 1]) sns.boxplot(datadf, x班级, y分数, axaxes[1, 0]) sns.violinplot(datadf, x班级, y分数, axaxes[1, 1]) plt.tight_layout() plt.show()figsize控制整张画布大小单位是英寸。tight_layout()自动调整子图间距避免标签重叠。如果tight_layout效果不理想可以用subplots_adjust手动调plt.subplots_adjust(wspace0.3, hspace0.4)wspace是水平间距hspace是垂直间距都是相对于子图宽高的比例。5.2 图级函数与轴级函数的混用陷阱seaborn 的函数分两类图级figure-level和轴级axes-level。图级函数包括relplot、displot、catplot、lmplot、jointplot、pairplot它们自己创建画布不接受ax参数。轴级函数包括scatterplot、histplot、boxplot等接受ax。混用的典型错误是想用subplots组合displot和histplot结果displot不认ax报错。解决办法是统一用轴级函数或者用FacetGrid手动管理。图级函数返回的是一个FacetGrid对象可以通过.fig和.axes访问底层画布g sns.displot(datadf, x分数, col班级) g.fig.set_size_inches(12, 4)但这种方式比较绕不如一开始就规划好。我的原则是如果一张图里要混多种图形类型全用轴级函数加subplots如果只是同一种图分面用图级函数。5.3 成对关系图pairplot 的正确打开方式pairplot一次性画出所有数值变量的两两散点图和单变量分布适合快速扫一遍数据。但变量一多图会爆炸——n 个变量产生 n×n 个子图。超过 5 个变量就不建议用了或者用vars参数只选关键几列。sns.pairplot(df, vars[学习时长, 分数, 作业完成率], hue班级)hue参数让不同类别用不同颜色对角线上的分布图会变成按类别叠加的密度图。cornerTrue只画下三角省一半空间。pairplot的另一个用途是发现异常模式。比如某个变量的分布图出现两个分离的峰说明数据里可能混了两个不同来源的样本。这种发现比看统计量直观得多。6. 那些文档里不会写的实战经验6.1 中文显示字体配置的完整方案seaborn 默认字体不支持中文图上会显示方块。解决办法是配置 matplotlib 的字体import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 或 Microsoft YaHei plt.rcParams[axes.unicode_minus] False # 解决负号显示问题SimHei是黑体Microsoft YaHei是微软雅黑Windows 系统一般都有。Mac 系统可以用Arial Unicode MS或PingFang SC。Linux 服务器上如果没有中文字体需要先安装字体文件再配置。axes.unicode_minus False这行经常被忽略但不加的话负号会显示成方块。原因是默认的 Unicode 负号和某些中文字体不兼容。如果是在 Jupyter Notebook 里配置完字体后可能需要重启内核才生效。这个坑很隐蔽改完代码发现没变化以为是代码写错了其实是缓存问题。6.2 大数据量下的性能优化数据超过十万行时seaborn 的绘图会明显变慢尤其是散点图和pairplot。几个优化方向第一绘图前先采样。如果只是看趋势随机抽一万行足够df_sample df.sample(n10000, random_state42)第二用rasterizedTrue把散点图栅格化。矢量格式保存时每个点都是一个独立对象十万个点就是十万个对象文件巨大且渲染慢。栅格化后整张图变成位图文件小很多plt.savefig(output.pdf, rasterizedTrue)第三考虑用datashader或hvplot这类专门处理大数据的库。但那是另一个话题了pandas seaborn 的舒适区在万行级别。6.3 可复现性随机种子和版本锁定seaborn 有些图涉及随机过程比如kdeplot的带宽估计、stripplot的点抖动。不设随机种子每次运行图都不一样没法复现。import numpy as np np.random.seed(42)另外seaborn 不同版本的默认样式有差异。0.11 和 0.12 的默认调色板就不一样。如果图要用于正式报告建议在代码里显式指定所有样式参数而不是依赖默认值。更稳妥的做法是记录版本号import seaborn as sns print(sns.__version__)我一般会在项目里放一个requirements.txt把 pandas、seaborn、matplotlib 的版本都锁死。这样半年后重新跑代码图还是原来的样子。6.4 常见报错与快速排查报错信息常见原因解决办法ValueError: Could not interpret value列名拼写错误或列不存在检查df.columnsTypeError: unsupported operand数值列里混了字符串用pd.to_numeric(errorscoerce)转换图显示为空白show()在savefig()之前调用调整调用顺序中文显示为方块字体未配置设置font.sans-serif图例遮挡数据默认位置不佳用bbox_to_anchor移出绘图区RuntimeError: selected KDE bandwidth is 0数据全相同或样本太少检查数据或改用直方图这张表里的问题我基本都遇到过尤其是第一个和第三个。列名拼错时seaborn 的报错信息不会直接告诉你列不存在而是说无法解释这个值新手容易懵。养成画图前先print(df.columns.tolist())的习惯能省很多时间。7. 一套可复用的绘图工作流把上面这些串起来我日常的绘图流程大概是这样的第一步读数据后先跑df.info()和df.describe()确认列类型和数值范围。第二步根据分析目标确定图形类型——看分布用直方图看关系用散点图看对比用箱线图。第三步如果涉及分组把数据转成长格式。第四步用set_theme统一风格指定调色板。第五步画图调标签、图例、坐标轴。第六步savefig保存参数设全。第七步如果图要给别人看检查一遍中文显示和分辨率。这套流程不复杂但每一步都有细节。比如第三步的长格式转换很多人觉得麻烦就跳过了结果画分组图时用循环一个个画代码又长又容易错。其实melt一行就搞定前期多花三十秒后期省半小时。还有一个心得不要追求一张图表达所有信息。我见过有人把七八个变量塞进一张图颜色、形状、大小、分面全用上结果谁也看不懂。图是给人看的不是给机器看的。一张图讲清楚一件事多画几张比一张全能图有效得多。最后说一个关于调色板的细节。分类变量超过八组时任何调色板都会开始难以区分。这时候要么合并小类别要么改用分面。硬用颜色区分十几组图例比图还大就本末倒置了。我一般控制在六组以内超过就考虑分面或者只突出关键组、其余组用灰色。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑