做机器学习项目做得多了,你会发现一个很朴素的道理:拿到数据之后,第一步永远不是直接丢进模型,而是先花时间把数据"看明白"。这里说的"看明白",指的就是数据可视化探索。Python里的可视化工具不少,matplotlib够底层够灵活,但写起来繁琐;pandas内置绘图虽然方便,但统计图形支持有限。而Seaborn这个Python数据可视化库,恰好站在两者之间,配合DataFrame使用非常顺手,特别适合机器学习项目中的探索性数据分析。
去年我在整理某机器学习课程的笔记时,把Seaborn从头到尾过了一遍,越用越觉得它适合当作日常EDA的主力工具。这篇就把我整理过的核心用法、踩过的坑、以及一些实操经验一次性分享出来。如果你是刚接触机器学习的小白,或者已经会用matplotlib但觉得画统计图太费劲,这篇文章应该能帮你快速上手Seaborn,少走不少弯路。
1. Seaborn是什么,为什么机器学习项目绕不开它
1.1 为什么是Seaborn而不是只用matplotlib
matplotlib的能力没有任何问题,但它的设计哲学是"给你所有零件,你自己组装"。画一个带分组颜色、统计趋势线的散点图,你需要自己处理颜色映射、图例、回归拟合,代码量非常可观。Seaborn相当于把这些常见的统计图形封装成了现成的函数,一行就能搞定。
更关键的是,Seaborn并不是重新发明了一套绘图体系,它底层依然依赖matplotlib,只是提供了一个更贴近数据分析场景的接口。它内置了多种美观的主题和配色方案,默认输出就比matplotlib原始样式好看很多,几乎不需要额外调整就能放到PPT或者报告中。
用个生活化的类比:matplotlib是厨房里的整套刀具和锅具,什么都能做,但需要你自己切、自己配、自己掌握火候;Seaborn则是一份半成品菜包,食材和调料都配好了,你只需要按步骤下锅,出锅就色香味俱全。对于机器学习项目中高频出现的分布图、箱线图、热力图这类"标准菜",用Seaborn的效率要高得多。
1.2 与pandas和matplotlib的协同关系
Seaborn最顺手的一点,是它可以直接接受pandas的DataFrame列名作为参数。你不需要像matplotlib那样手动把数据拆成列表,只要把DataFrame传进去,指定x、y、hue分别对应哪一列,剩下的分组、聚合、图例都由Seaborn自动完成。
这种设计对机器学习流程很友好。因为项目里的数据基本都是DataFrame,特征工程做完之后,想要快速看某个特征与标签之间的关系,代码就只是seaborn.scatterplot(data=df, x='feature', y='target', hue='label')这样几行。
它和matplotlib的关系也不是替代,而是互补。Seaborn负责画统计图,matplotlib继续负责那些高度定制的细节,比如坐标轴刻度格式化、添加文字批注、调整子图布局。实际项目中,常用的组合是"Seaborn画主体,plt来修饰"。你需要记住一句话:Seaborn是matplotlib的增强层,画图的底层对象依然是Axes,所以plt.title()、plt.xlabel()这些函数依然可以作用于Seaborn生成的图。
1.3 环境准备:安装与导入
安装非常直接,如果有conda环境,执行conda install seaborn,或者直接用pip:
pip install seabornSeaborn依赖numpy、pandas和matplotlib,安装时通常会自动带上。我建议在虚拟环境里安装,避免和系统环境打架。导入时一般约定写成:
import seaborn as sns import matplotlib.pyplot as plt import pandas as pd import numpy as npseaborn的官方简写是sns,这是长期形成的习惯,社区里所有教程、文档都这么用。如果哪天看到别人的代码里出现sns,别疑惑,就是Seaborn。
安装完成后,可以加载Seaborn自带的数据集来练手。sns.load_dataset('tips')、sns.load_dataset('iris')、sns.load_dataset('titanic')都是经典数据集,非常适合用来熟悉各种图表。第一次联网加载时可能会提示下载,缓存到本地之后速度就快了。
2. 机器学习项目里最常用的几种统计图形
2.1 单变量分布图:历史数据长什么样
机器学习第一步通常是看每个特征自身的分布。特征是否接近正态分布、有没有明显偏态、有没有异常值,这些信息直接影响后续的标准化方案和模型选择。
Seaborn里最常用的单变量分布图是histplot和kdeplot。histplot是直方图,适合看频数分布;kdeplot是核密度估计曲线,相当于把直方图平滑成一条连续曲线,更适合看分布形状。在新版本里,sns.histplot(data=df, x='total_bill', kde=True)可以同时画出直方图和密度曲线,一行代码就能完成常见的"分布体检"。
画分组分布也很简单。比如想看男女顾客消费金额的分布差异,只要加一个hue参数:
sns.histplot(data=df, x='total_bill', hue='sex', kde=True, alpha=0.6)或者用multiple='stack'把不同组的直方图堆叠起来,对比效果更直观。这个需求放在matplotlib里,你需要手动循环分组、多次调用hist、再自己写图例,但在Seaborn里就是加一个参数的事。
2.2 变量关系图:两个特征之间有没有关联
在探索特征与特征、特征与标签之间的关系时,散点图是首选。scatterplot是基础版,relplot是它的"高级形态",支持用hue、size、style同时映射多个维度的信息。
sns.scatterplot(data=df, x='total_bill', y='tip', hue='sex', size='size', alpha=0.7)这样一张图,横轴是消费金额,纵轴是小费,颜色区分性别,点的大小代表用餐人数,信息量比普通散点图丰富得多。
当数据量太大、散点严重重叠时,纯散点图会糊成一团。这时候可以换成jointplot,它中间是散点或密度图,四周还附带单变量分布:
sns.jointplot(data=df, x='total_bill', y='tip', kind='hex')kind='hex'会用六边形蜂窝图展示密度,颜色越深表示该区域点越密集,非常适合大数据量。
如果要一下子看多个特征两两之间的关系,pairplot是最省事的方案。它会自动把DataFrame里的数值列做两两组合,对角线画单变量分布,非对角线画散点图:
sns.pairplot(data=df, hue='sex')当hue指定分组后,所有子图都会自动带上颜色和图例。在机器学习项目里,pairplot常被用来做特征相关性速览。不过要注意,特征数量太多时子图数量会爆炸式增长,一般只挑少数核心特征来画。
2.3 分组对比图:不同类别之间有显著差异吗
分类特征对比是EDA的重点,比如"不同性别的消费是否存在差异""周一和周五的小费分布差多少"。这类问题用箱线图和提琴图最合适。
boxplot可以很方便地展示分位数和异常值:
sns.boxplot(data=df, x='day', y='total_bill', hue='sex')箱体上下边界是四分位数,中间的线是中位数,箱体外的点是异常值。一份图就能看出中心位置、离散程度和异常情况。
violinplot在箱线图基础上加上了核密度估计的形状,能更细致地反映分布形态,适合描述多峰分布。split=True还能把两个分组的提琴图左右对称拼在一起,对比效果非常直观:
sns.violinplot(data=df, x='day', y='total_bill', hue='sex', split=True)对于计数类数据,比如不同日期、不同性别的订单数量,用countplot最简单。它本质上就是分类柱状图,直接统计每个类别出现的次数。而barplot可以展示某个类别对应的数值特征的均值,并自动加上误差棒,相当于帮你算好了分组统计结果:
sns.barplot(data=df, x='day', y='total_bill', estimator=np.mean)不过要提醒一句:barplot的默认误差棒在一些新版里展示的是置信区间,如果你更想要标准差,记得手动用errorbar='sd'这类参数控制,不同版本写法略有差别。
2.4 相关性热力图:找出冗余特征
机器学习特征工程里有一项重要工作:检查特征之间的相关性。高度相关的特征往往会带来信息冗余,影响模型的可解释性。Seaborn的heatmap是展示相关性矩阵的标准工具中的标准工具。
corr = df.select_dtypes(include=[np.number]).corr() sns.heatmap(corr, annot=True, fmt='.2f', cmap='coolwarm', square=True)annot=True会在格子里显示相关系数数值,fmt='.2f'控制显示的小数位数,cmap指定色带,square=True让格子呈正方形。这张图在看多特征数据时非常直观:颜色越深表示相关性越强,从中能快速发现哪些特征高度共线。
如果你只想看上三角部分,可以提前用numpy生成一个掩膜矩阵:
mask = np.triu(np.ones_like(corr, dtype=bool), k=1) sns.heatmap(corr, mask=mask, annot=True, fmt='.2f')这种写法在很多Kaggle比赛方案里都能看到,生成的热力图更整洁,信息也没有丢失。
3. 主题、配色与细节控制:让图表能直接放进报告
3.1 一套配置统管全图风格
Seaborn的默认样式已经不错,但项目汇报时,通常希望整套图风格统一,看起来像是同一个流水线出来的。这就用到set_theme:
sns.set_theme(context='notebook', style='darkgrid', palette='deep', font='SimHei')context控制整体比例,分为paper、notebook、talk、poster四档。写报告选paper,屏幕演示选notebook或talk,大屏汇报选poster。style控制背景和网格线,有white、dark、whitegrid、darkgrid四种可选。我个人习惯用darkgrid,因为网格线能帮助读图,深色网格在投影上也清晰。
如果你不想用全局配置,只想局部调整一张图,可以用axes_style配合with语句,限制影响范围:
with sns.axes_style('whitegrid'): sns.boxplot(data=df, x='day', y='total_bill')这种写法很适合只对某一张图做特殊处理的场景。
3.2 调色板:颜色不抢戏但也不能看不懂
Seaborn的调色板系统非常强大,基本上不需要自己手写十六进制颜色。palette='deep'是默认的强色调,适合分类数据;palette='pastel'柔和一些;palette='husl'色相分布均匀,适合类别多的情况。
连续型数据或相关性热力图,一般用渐变色带,可以通过cmap参数直接传入matplotlib的色带,比如coolwarm、viridis、RdBu_r。其中RdBu_r在热力图上很常用,正负相关分别用红蓝表示,视觉上非常容易区分。
如果你需要一套自己定义的颜色,可以用color_palette生成一组颜色列表:
custom_colors = sns.color_palette(['#3498db', '#e74c3c', '#2ecc71']) sns.boxplot(data=df, x='day', y='total_bill', palette=custom_colors)这里要注意,新版Seaborn里对部分绘图函数传palette可能不再生效,而是要用hue来映射,或者直接给color参数传单一颜色。不同函数行为不完全一致,遇到调色板不生效时优先查一查当前版本的官方文档。
3.3 坐标轴、标题、图例的微调
Seaborn生成的图本质上是matplotlib的Axes对象,所以标题、坐标轴标签、图例位置的微调都需要用matplotlib的函数。
sns.histplot(data=df, x='total_bill', kde=True) plt.title('消费金额分布') plt.xlabel('总消费(美元)') plt.ylabel('频数') plt.tight_layout()图例稍微麻烦一点。Seaborn会自动创建图例,但默认位置不一定合适,有时候会盖住数据。简单粗暴的解决办法是手动指定位置:
plt.legend(title='性别', loc='upper right', frameon=False)如果图例项太多,还可以改成两列显示:plt.legend(ncol=2)。在机器学习项目里,图例不要花哨,frameon=False去掉边框会让整个图干净不少。
3.4 中文字体配置
Seaborn默认使用英文环境下的字体配置,直接显示中文会变成方块。这个问题几乎每个中文用户都会遇到。标准解决方法是回到matplotlib层面设置字体:
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'Arial Unicode MS'] plt.rcParams['axes.unicode_minus'] = Falseaxes.unicode_minus必须设置,否则坐标轴上的负号会显示成方块。MacOS可以换成Arial Unicode MS,Linux可以装文泉驿字体。这个配置建议放在notebook或脚本的最开头,和sns.set_theme放在一起。
4. 实操流程:从原始数据到一张完整EDA报告
4.1 数据准备与初步体检
我用Seaborn自带的tips数据集走一遍完整流程,大家都拿得到,方便复现。先加载数据:
import seaborn as sns import matplotlib.pyplot as plt import pandas as pd import numpy as np sns.set_theme(context='notebook', style='darkgrid', palette='deep') plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei'] plt.rcParams['axes.unicode_minus'] = False df = sns.load_dataset('tips') print(df.head()) print(df.info()) print(df.describe())info()可以看列名和缺失值,describe()看数值列的统计量。先做这一步不是为了展示,而是帮你决定后续要画哪些图。比如发现某个类别样本很少,后面的分组图就要谨慎解读。
4.2 单变量分布检查
先看目标变量tip,它决定了模型要预测什么:
plt.figure(figsize=(8, 5)) sns.histplot(df['tip'], kde=True, bins=30) plt.title('小费金额分布') plt.show()从图上能明显看到右偏分布,这意味着如果直接用线性模型,误差会不太稳定。在真实项目中,看到这样的分布,下一步就会考虑对目标变量做log变换。这就是单变量图的价值:它直接指导特征工程。
接下来看数值特征total_bill,同样画分布图,并且按性别分组:
plt.figure(figsize=(8, 5)) sns.histplot(data=df, x='total_bill', hue='sex', kde=True, alpha=0.5) plt.title('消费金额分布(按性别)') plt.show()这里如果两组分布重叠程度很高,说明性别对消费金额的影响有限。如果分离明显,性别就是一个值得保留的特征。
4.3 多变量关系与分组对比
探索total_bill和tip的关系,这是数据集里最核心的两个数值变量:
plt.figure(figsize=(8, 5)) sns.scatterplot(data=df, x='total_bill', y='tip', hue='time', style='smoker', size='size') plt.title('消费金额与小费的关系') plt.show()散点图能明显看到正向线性关系。此时可以继续用lmplot画带回归拟合线的散点图:
sns.lmplot(data=df, x='total_bill', y='tip', hue='time', height=6)lmplot会自动拟合线性回归并画出置信区间,一图看完关系强度、方向和异常点,模型选型时非常有参考价值。
再看分类特征之间的差异。比如星期几对消费金额的影响:
plt.figure(figsize=(8, 5)) sns.boxplot(data=df, x='day', y='total_bill', order=['Sun', 'Sat', 'Thur', 'Fri']) plt.title('不同日期的消费金额分布') plt.show()手动指定order很重要,否则Seaborn默认按字母顺序排列,周一和周五的顺序就会很奇怪。箱线图能看出周末的中位数明显高于工作日,这也是一个可以继续深挖的业务洞察。
最后用heatmap把所有数值特征的相关性放到一张图上:
corr = df.select_dtypes(include=[np.number]).corr() plt.figure(figsize=(6, 5)) sns.heatmap(corr, annot=True, fmt='.2f', cmap='coolwarm', square=True, cbar=True) plt.title('数值特征相关性热力图') plt.show()由于tips数据集的数值列不多,这张图很简单,但换成真实项目的几十个特征时,这张图能帮你快速锁定高度共线的特征组合,为后续特征筛选提供依据。
4.4 把整套图组织成一个网格
如果不想一张一张地画,可以用FacetGrid一次性生成多子图。比如按性别和时间分组,展示消费金额分布:
g = sns.FacetGrid(df, col='time', row='sex', margin_titles=True) g.map(sns.histplot, 'total_bill', kde=True) g.add_legend() plt.show()这种网格图在对比多个条件下的分布时非常够用。FacetGrid几乎可以对接任何Seaborn绘图函数,相当于用DataFrame的组别自动切分子图。对机器学习EDA来说,这种"分面"方式比单纯多画几张图更省版面,也更便于对比。
同时,还可以用pairplot快速浏览所有数值列的关系:
sns.pairplot(df[['total_bill', 'tip', 'size']], diag_kind='kde')diag_kind='kde'可以让对角线上的单变量图用密度曲线显示。这个函数适合小规模特征集的快速体检,十几列以内用起来最舒服。
4.5 高质量图片输出
做完整套EDA,最后一步是保存图片。我常用的参数是:
plt.savefig('eda_total_bill.png', dpi=300, bbox_inches='tight', facecolor='white')dpi=300保证印刷清晰,bbox_inches='tight'自动裁剪空白区域,facecolor='white'避免透明背景在Word或PPT里变成黑块。如果图片要放到论文里,可以考虑输出成PDF矢量格式:
plt.savefig('eda_total_bill.pdf', bbox_inches='tight')矢量图放大不会模糊,推荐在写技术报告时使用。
5. 常见坑与排查技巧
5.1 版本升级带来的API变化
Seaborn版本迭代比较快,特别是在0.11到0.13版本之间,很多旧函数被标记为废弃或者改版。最典型的就是sns.distplot,旧代码里经常出现,但在新版本中会抛FutureWarning,建议改成histplot或kdeplot。
如果你跑别人的老代码遇到警告,别慌,按提示改函数名就行。distplot的功能基本被拆分成histplot和kdeplot,把原来的参数对应过去即可。另外一个变化是barplot里的ci参数改成了errorbar,旧代码里的ci=None需要改成errorbar=None。
遇到这类问题,最效率的方法是查看当前版本官方文档里的"release notes",里面会把每个废弃接口的迁移方案写清楚。千万别对着老博客里的代码硬抄,很多代码在新环境里跑不通就是这么来的。
5.2 中文乱码和负号问题
中文显示成方块、负号显示成方块,这是最影响心情的问题。前面说过,解决办法是通过rcParams设置字体。这里再补充一点:如果设置了中文字体后,X轴或Y轴的刻度标签仍然出现小方块,可能需要清除matplotlib缓存:
import matplotlib as mpl print(mpl.get_cachedir())然后把缓存目录清空,重启Python环境。这个坑有时候很隐蔽,明明字体装了、配置也写了,但图还是乱码,多半就是缓存问题。
5.3 类别顺序和图例不按预期
画箱线图时默认按字母排序,想让类别按业务逻辑排列,要用order参数。比如order=['周一', '周二', ...]。这个方法适用于boxplot、violinplot、barplot、countplot等大部分分类图。
如果order也不管用,可以在DataFrame层面把列转成category类型并指定categories顺序:
df['day'] = pd.Categorical(df['day'], categories=['Thur', 'Fri', 'Sat', 'Sun'], ordered=True)这样不用在每个绘图函数里都传order,全局一次搞定。图例位置问题则用plt.legend手动调整,前面已经说过。
5.4 数据量大时绘图卡顿
Seaborn本质上把所有数据都交给matplotlib渲染,数据量一大,散点图会卡到怀疑人生。我有一次画几十万条记录的散点图,浏览器直接不动了。解决办法有几个思路:
- 直接采样:
df.sample(10000),适合EDA阶段快速看趋势。 - 用
histplot加hue代替散点:用二维密度方式展示分布,性能更好。 - 在绘图参数里加
rasterized=True,把图像栅格化,保存时矢量部分会变少,导出PDF时不会卡。 - 用
scatterplot的alpha参数降低点的不透明度,减少视觉重叠。
sns.scatterplot(data=df.sample(5000), x='x', y='y', alpha=0.1, rasterized=True)真实项目里,几十万行的数据集很常见,EDA阶段不需要把所有点都画出来,抽样完全不影响观察趋势。
6. 实践经验与效率技巧
6.1 把常用绘图封装成函数
写多了会发现,EDA的绘图操作高度重复。每次都要写标题、写标签、调整图例,非常枯燥。我习惯把常用图表封装成小函数,比如"给任意两列画关系图":
def plot_scatter(df, x_col, y_col, hue_col=None, title=''): plt.figure(figsize=(8, 5)) sns.scatterplot(data=df, x=x_col, y=y_col, hue=hue_col, alpha=0.7) plt.title(title) plt.tight_layout() plt.show()封装的思路不追求复杂,只要把最常用的参数暴露出来就行。这样每次拿到新数据,调用几分钟就能生成整套EDA图,省下来的时间足够多做一轮特征分析。
6.2 用PairGrid和FacetGrid替代手写循环
当你想对多个特征分别画图时,手写循环是下策。FacetGrid可以在一个图里按条件切分多个子图,自动适配所有数据子集。PairGrid则更适合自定义配对图,可以自由组合上三角、下三角和对角线的图形类型:
g = sns.PairGrid(df[['total_bill', 'tip', 'size']], diag_sharey=False) g.map_upper(sns.scatterplot) g.map_lower(sns.kdeplot, alpha=0.5) g.map_diag(sns.histplot, kde=True)这种灵活度在真实项目中非常实用。比如上三角画散点,下三角画密度图,对角线画直方图,一张图涵盖的信息量远超普通pairplot。
6.3 与机器学习流程的衔接
Seaborn在EDA和结果分析阶段都有用武之地。特征工程阶段,用它画分布图和相关性图;模型训练之后,用它画预测值与真实值的散点图、残差分布图、特征重要性图。比如我经常画残差图:
residuals = y_test - y_pred sns.histplot(residuals, kde=True, bins=30)或者用回归图评估预测值的拟合程度:
sns.scatterplot(x=y_test, y=y_pred, alpha=0.6) plt.plot([y_test.min(), y_test.max()], [y_test.min(), y_test.max()], 'r--')这些图和EDA阶段的图风格统一,整理成报告时非常省心。
6.4 学习路径建议
如果你是新手,我的建议是从这三个数据集开始:tips、iris、titanic。先把histplot、scatterplot、boxplot、barplot、heatmap这五个主力图型熟练起来,再逐步接触lmplot、pairplot、FacetGrid这类进阶工具。
不要试图一次性记住所有函数参数。Seaborn的核心思路就是"分类数据、关系数据、分布数据",你只需要记住每个类别下有哪些函数,用的时候查文档即可。真正需要理解的是:每一张图回答什么问题、什么时候应该用什么图。图表工具会频繁更新,统计图形的基本选择逻辑不会变。把注意力放在"这个特征分布是不是偏态""这两个特征是否相关""不同分组之间的差异是不是显著"这些问题上,Seaborn自然越用越顺手。
我自己在多个项目里反复用下来,最大的感受是:Seaborn最大的价值不是帮你画出一张好看的图,而是让你把更多时间留给数据本身。机器学习项目里,数据质量决定模型上限,而数据可视化是发现数据问题的第一道照妖镜。希望这份笔记能让你少走一些弯路。