ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

seaborn进阶绘图全指南:从图形网格到分布密度与回归诊断

seaborn进阶绘图全指南:从图形网格到分布密度与回归诊断 先说个我自己的经历。去年重新整理一份数据分析报告最初用matplotlib手工拼了二十几张单变量图结果变量之间什么关系都看不出来汇报时被连续追问这两个特征到底有没有关联分群之后分布差在哪当场翻车。后来换成seaborn的处理方式同样的数据几张网格图和密度图就把问题讲清楚了。这篇文章聊的就是seaborn绘图里偏进阶的部分图形网格怎么帮你一次性梳理多组变量、分布图怎么从看轮廓升级到读密度、分类数据对比怎么选图、回归趋势线背后有哪些坑以及主题配色和版本迭代里那些容易忽略的细节。基础绘图在上篇已经讲过这篇的主线是如何用seaborn做真正的多变量探索。如果你是刚上手seaborn的读者建议先把histplot、boxplot、scatterplot这些单图用熟再回来看这篇如果你已经画过一阵子下面这些内容大概率能帮你解决图是画出来了但总觉得信息密度不够的问题。1. 图形网格一次看完全部变量组合seaborn最容易被低估的能力其实是网格绘图。很多人把它当作matplotlib的换皮工具画单张图用用真正需要对比多变量关系时又退回matplotlib的subplot手工排版。但seaborn真正值钱的地方恰恰在这里你用数据框的列名直接声明按什么分组、按什么分面、hue染什么色剩下的坐标轴共享、图例、子图标题全部由库内部处理。1.1 FacetGrid按条件拆分数据的正交视角FacetGrid适合我想看某个关系在不同条件下的表现。比如tips数据集里小费金额和消费总额的关系在午餐、晚餐、是否吸烟人群里可能完全不同。常规做法是画四张散点图慢慢比对用FacetGrid只需要把条件写进col和row参数import seaborn as sns import matplotlib.pyplot as plt tips sns.load_dataset(tips) g sns.FacetGrid( tips, coltime, rowsmoker, huesex, height4, aspect1.1, sharexTrue, shareyTrue ) g.map(sns.scatterplot, total_bill, tip, alpha0.7) g.add_legend() plt.show()这里面有三个点值得讲。第一col和row就是两个切分条件col向右展开、row向下展开形成一个正交矩阵。第二sharex和sharey默认是True也就是说所有子图共享坐标轴方便你直接比较不同条件下的分布范围。如果不同分组的数据量纲差异特别大比如一组数值在0到10、另一组在100到10000这时候你需要把sharex或sharey关掉否则小量纲的分组会被压成一条线。第三g.map传入的不是子图配置而是一个绘图函数和对应的数据列名FacetGrid会把这个函数应用到每一个分面上。这里有个常见坑当你想给绘图函数传额外参数时直接写在map里可能不生效。比如想给散点图固定s参数你应该用g.map(sns.scatterplot, total_bill, tip, s20)这样的写法参数会被透传给scatterplot。但遇到比较复杂的函数或者你需要做多列计算后再绘图那就得改用g.map_dataframe它能把你需要的函数签名改为接收整个子集DataFrame灵活性高很多。col_wrap参数也值得记一笔。当分组条件很多时不想让子图排成很宽的一行可以设置col_wrap3让子图每行最多3个自动换行。不过要注意col_wrap生效时不能再配合row参数使用这是设计上的限制硬要同时用会报错。1.2 PairGrid对角线、上三角、下三角各干各的PairGrid解决的是无条件的多变量互看问题。它把数据框里的数值列两两配对对角线放单变量分布上下三角放散点或者密度图。直接调用pairplot是最省事的方案但默认行为不一定是最优解。拿penguins数据集举例我想看物种在多个数值特征上的区分度同时样本量又不算小penguins sns.load_dataset(penguins) g sns.PairGrid( penguins, huespecies, cornerTrue ) g.map_diag(sns.histplot, kdeTrue) g.map_lower(sns.scatterplot, alpha0.6) g.add_legend() plt.show()PairGrid最关键的是三个方法map_diag只画对角线map_offdiag负责所有非对角线位置map_upper和map_lower则只作用于上半区或下半区。实际操作里我会用cornerTrue只保留下三角因为上三角和下三角内容重复纯属浪费版面。对角线用histplot加kde既能看分布形态又能保留数据量信息下三角用散点加透明度重叠区域能靠alpha看出密度。如果你确实需要hue分色PairGrid的add_legend会把分组的图例自动补上而且图例位置默认在右上方不会压住数据。这里提醒一句pairplot虽然方便但对大数据集不友好——列数一多非对角线散点图的数量是组合数增长5个数值列就有10张散点图6列就是15张。数据量超过几万行时纯散点会糊成一团。我的做法是换hexbin或者kdeplot放到非对角线再不济就降低采样先看结构再谈细节。1.3 JointGrid的中心图加边缘分布JointGrid是介于FacetGrid和PairGrid之间的存在它只处理一对变量但把中间的主图与顶部的x分布、右侧的y分布组合在一起。这个布局最常用于两个变量相关关系 各自分布形态的场景。jointplot是快捷入口kind参数可以切换scatter、kde、hex、reg等。我常用的组合其实是手动控制JointGridg sns.JointGrid(datatips, xtotal_bill, ytip) g.plot(sns.scatterplot, sns.histplot) plt.show()plot方法接受两个函数第一个画中心图第二个画边缘图。如果中心图想用密度等高线可以写成g.plot(sns.kdeplot, sns.histplot)。更进阶一点你可以在JointGrid上分hue做对比比如g sns.JointGrid(datatips, xtotal_bill, ytip, huesex)然后对每个hue分组分别scatter和kdeseaborn会处理好颜色映射边缘密度图也会跟着分组。JointGrid的实用性在于汇报场景一张图既讲关系又讲分布省掉用户来回比对两张图的时间。这个布局在论文配图里也常见因为审稿人会直接看到x和y各自的分布是否正常、有没有离群点影响回归结论。2. 分布绘图从看轮廓升级到读密度直方图的最大问题是bin宽度敏感——同样的数据bin取30和取80呈现出的分布形态可能完全不同。seaborn从0.11之后全面转向了histplot和kdeplot目的就是让你摆脱这种人为参数带来的偏差。2.1 KDE带宽为什么默认值不一定适合你的数据KDE做的事情可以通俗理解为在每一个样本点上放置一个小核函数然后把所有核函数叠加平滑得到一个连续的密度估计。这里的核宽度就是带宽seaborn里对应bw_adjust参数。带宽越大曲线越平滑但细节可能被抹掉带宽越小曲线越贴近样本但可能出现一堆虚假的毛刺。默认值bw_adjust1.0不是一个坏选择但它基于的是数据的标准差对多峰分布的适应能力一般。我有一次分析某个产品的时长数据分布呈现出典型的双峰一个小峰对应快速放弃用户一个大峰对应重度用户。默认KDE画出来只有中间一个平缓的驼峰两个峰被糊在一起差点导致结论做反。把bw_adjust调到0.6之后双峰结构立刻清晰了。sns.kdeplot(datadf, xduration, bw_adjust0.6, cut0)cut参数控制的是曲线往数据范围两侧延伸多远。默认情况下KDE会在数据边界外侧拖一条长长的尾巴在真实数据上这可能给人错误的暗示好像数据范围之外还有不少样本。cut0会强制曲线在数据边界处截断。如果数据有明确的下界比如时长不可能为负数还可以配合clip参数clip(0, None)告诉KDE只估计大于等于0的范围否则负半轴的密度毫无意义。2.2 多元KDE与边缘分布解读等高线二维KDE在seaborn里画出来是等高线图很多人看不太懂。它和一维KDE的逻辑完全一样——每个样本点贡献一个小鼓包多个鼓包叠加后形成地形等高线就是相同密度值的连线。密度越高的区域等高线越密集中心区域颜色越深。sns.kdeplot( datatips, xtotal_bill, ytip, fillTrue, levels8, thresh0.05 )这里有两个参数建议根据数据量调整levels控制等高线层数默认并不总是好看我一般取5到10之间thresh表示低于多少密度的区域不填充默认0.05可以避免极端稀疏区域被大片浅色覆盖。如果数据分布特别集中等高线会挤在一小块区域里此时可以把levels调细让内部结构露出来。jointplot里kindkde会自动帮你把二元KDE和两侧边缘密度放在一起比散点图更适合展示高重叠数据。当样本量只有几百时散点完全分不清局部密度KDE的等高线却能清楚显示数据集中在一个斜带里。不过要留意KDE不适合离散性极强的数据比如大量0和1构成的伯努利分布画出来会是一条诡异的连续山脊这时候直接histplot按类别看反而准确。2.3 rugplot和ecdf在密度图旁边放点原始痕迹密度图平滑过度容易给人数据很连续的错觉事实上原始样本可能只有几十个点。rugplot就是在坐标轴上画一排短线每个观测值一根让读者一眼看到真实的采样稀疏程度。它适合和KDE配合也适合临时候补说明。更稳的补充是ECDF图。seaborn里有ecdfplot它不涉及任何带宽或者bin的选择纯粹按数据从低到高累积比例能忠实反映分布。它的优势在做分布对比时尤其明显多条ECDF曲线可以直接看出某个取值上两组样本的占比差异不受平滑参数影响。sns.ecdfplot(datatips, xtotal_bill, huetime)实际报告里我常把KDE和ECDF同时给出KDE给人直观的形态感ECDF提供严格的可读性。两者配合比单独一张密度图可信得多。3. 分类数据对比选对图形才能说对结论分类变量和连续变量组合时可选的图形很多但每种图强调的信息侧重点完全不同。很多人习惯直接用barplot看均值排名这在很多场景下其实掩盖了大量分布信息。3.1 箱线、小提琴、蜂群图的分工箱线图展示四分位数和离群点信息压缩度高适合快速扫描多个分组。缺点是它看不出来分布是不是双峰的——两组数据可能拥有完全一致的箱线图但分布形态天差地别。小提琴图解决的就是这个问题它在箱线图基础上并排画了两个密度曲线宽度表示密度大小。seaborn的小提琴图还可以配合split参数做两个组的背靠背比较sns.violinplot( datatips, xday, ytotal_bill, huesex, splitTrue )splitTrue只在hue只有两个水平时使用两组各占半边。这种图的特点是直观缺点是当样本量较小时密度估计本身就不稳定反而不如箱线图老实。蜂群图stripplot和蜜蜂图beeswarm是另一个极端它们把每个观测值都画出来用细微偏移避免点与点完全重叠。能看清每一条原始数据但是样本量一大就退化成色块。当数据量在几百以内时我最喜欢的是小提琴图外轮廓加内部蜂群点的组合具体实现是先画小提琴图再叠加stripplot并控制透明度sns.violinplot(datatips, xday, ytotal_bill, innerNone, alpha0.3) sns.stripplot(datatips, xday, ytotal_bill, alpha0.5, jitter0.2)这种组合的信息量是任何单图都比不上的外部轮廓告诉你分布形状内部点告诉你真实的样本位置和稀疏程度两者互为印证。加个透明度就能避免图案糊在一起这是我在报告里用的最多的分类对比方案。3.2 catplot一键切换kind与hue分离策略catplot是分类绘图的总入口kind参数可以在box、violin、boxen、strip、swarm、point、bar之间自由切换。它的价值在于faceting能力像FacetGrid一样同时按其他条件拆分子图。sns.catplot( datatips, xday, ytotal_bill, kindboxen, huesmoker, coltime, height4 )boxen图很多人不熟它是箱线图的增强版画出更多的分位数层级能展示出比普通箱线图更细的尾部结构适合样本量较大的数据。在探索阶段用catplot地毯式扫描所有分类变量的组合非常高效某个type看腻了直接换kind代码改动很小。这里需要提醒的是pointplot和barplot。barplot的纵轴默认是均值误差条表示置信区间但它容易让读者误以为柱子的高度代表了数据的整体分布。pointplot更适合展示分组均值的变化趋势尤其在分类变量本身有序时效果很好。如果你只是想让报告更专业我的建议是普通情况下不要用barplot展示连续变量优先boxen或violin它们不会骗人。hue分离上要注意dodge参数。默认情况下hue存在时箱线图和小提琴图会把不同颜色分组错开排列这叫dodgeTrue。但当样本量不平衡时错开排列会让人误以为两个组的样本量相近。这种语境下可以考虑dodgeFalse让两组在同一个位置重叠显示虽然更乱但至少不会制造假象。4. 回归与关系趋势线背后的坑与选择seaborn的regplot和lmplot画的不只是散点加直线它们背后真正做的是用最小二乘法拟合一个统计模型并把模型结果可视化。这个定位决定了它们跟纯画图工具不一样——你选择什么模型形式直接决定读者看到什么结论。4.1 regplot和lmplot什么时候该用哪一个两者都画回归线加置信带。regplot是一个轴级函数接受两个或三个变量的数据lmplot是FacetGrid的封装适合按类别拆分回归关系。实际使用中如果是单组的x-y回归我用regplot如果我想看性别和吸烟状态对消费金额-小费关系的调节作用用lmplot更省事sns.lmplot( datatips, xtotal_bill, ytip, huetime, colsmoker, height4, ci95 )这样每个小图是一组条件下的回归线hue再拆一层相当于把交互效应直接画在图上。ci参数控制置信带的宽度默认95%。如果在做汇报时嫌置信带太占视觉比重可以临时设ciNone但要记住这只是视觉简化不是统计上取消。4.2 非线性关系与稳健拟合默认画的是直线但真实关系往往不是线性的。regplot和lmplot都支持order参数做多项式拟合order2就是二次多项式。比如广告投入和转化率常呈现先增后平的形态二次项能比直线拟合得更贴近实际。需要注意的是多项式阶数越高越容易过拟合尤其是样本量小、x范围两端数据稀疏的时候三次以上的高阶项经常会画出非常怪的甩尾形状。我个人的底线是order2或3更高阶就换别的建模手段去验证。更稳健的选择是lowess局部加权回归。它不对全局函数形式做假设而是逐段拟合加权回归特别擅长捕捉非单调、非对称的关系。用法是加lowessTrue。但lowess的结果没有置信带也不给出系数所以它适合探索性分析不适合需要明确参数解释的场景。还有一个容易忽略的参数是logistic。当你的y是0/1二分类变量时regplot默认会画一条普通直线这在统计上是无效的因为预测值会超出0到1的区间。设置logisticTrue之后seaborn会做逻辑回归拟合画出一条S形曲线这才符合概率的语义。这个参数对于做分类问题的人非常实用但知道的人不多。4.3 别跳过残差图拟合完回归线下一步就该画残差图了。residplot把每个点的实际y值减去拟合值得到残差再画出来。如果残差均匀分布在0附近且没有明显模式说明线性假设基本成立如果残差呈现喇叭形散开或者曲线形态说明模型形式有问题。sns.residplot(datatips, xtotal_bill, ytip, lowessTrue)这个图我几乎每次都画但周围很多同事会跳过。实际上残差图的模式往往比R平方更能告诉你模型的局限。我已经数不清多少次因为残差图发现某个分组被系统性地低估而调整特征。5. 主题与配色摆脱默认脸但别过度装饰seaborn的默认主题帮你省了很多事但如果你直接把默认样式放进论文或者对外报告看起来就有点Python教程味。主题系统值得花一点时间理解它不是换肤而是让图表在不同媒介下自动保持可读性。5.1 六套基础色板的内在逻辑seaborn自带的六套定性色板deep、muted、bright、pastel、dark、colorblind。它们之间不是简单的颜色不同而是饱和度和明度的设计取向不同。deep和muted比较均衡适合大部分图表bright饱和度更高适合浅色背景下的强调pastel饱和度低适合柔和风格但投影时对比度不足dark适合深色背景colorblind专门为色觉障碍读者优化这不该是最后的选项而应该是你做对外物料时的默认。sns.set_palette(colorblind)如果内置色板不够用可以用color_palette定制。sns.color_palette(husl, 8)给出8个在色相环上均匀分布的颜色sns.light_palette(seagreen)从浅到深渐变做热力图时常用的连续映射可以直接取rocket、mako、crest这些内置顺序色板。一个我常用的操作是翻转crest_r表示反向让深色对应高值还是低值取决于你的语义习惯。5.2 context一图多用的字号缩放同一张图投在屏幕上看和放进200%缩放的论文里字号需求完全不同。seaborn用context参数解决这个问题set_context有四个预设paper、notebook、talk、poster分别对应从小到大的字号和线条粗度。sns.set_context(paper)我通常在数据探索阶段用notebook字够大看得清做论文配图时切到paper做PPT汇报时用talkposter适合做大幅海报字号大但图例容易溢出。内置context不够精细时可以直接改sns.plotting_context里面的rcParams比如单独把轴标签字号调大、网格线调淡。这个机制比手动逐张设置matplotlib参数清爽得多。5.3 自定义样式与一次性全局生效set_theme是终极入口它一次设置style、palette、context和font相当于给matplotlib全局rcParams套了一层seaborn语义。比如sns.set_theme( styleticks, palettemuted, contexttalk, font_scale1.1 )style的五种选择是white、dark、whitegrid、darkgrid、ticks。grid对数据分析很有用但对外展示时我倾向于ticks或者white网格线会干扰信息。用axes_style可以局部微调比如把网格线颜色调浅或者给坐标轴加个边框。我的习惯是全局set_theme定基调再做一两处局部微调不做花哨装饰。颜色也好、字体也好克制是第一原则图表的任务是传信息不是炫技。6. 安装、升级与API变化遇到下载seaborn之后的事很多人在查seaborn库下载以为pip之后就万事大吉了实际用起来才发现版本不同API差得离谱。distplot这个函数在早期教程里出现频率极高但现在已经被移除。如果你照着老博客的代码跑第一行就会报AttributeError。这类问题比画图本身更消耗时间值得单独说一次。6.1 安装依赖中容易被忽视的版本组合seaborn是建立在numpy、pandas、matplotlib之上的pip install seaborn会自动带上这些依赖。但如果你用的是conda环境或者公司内部的lock文件依赖版本可能被锁定在偏旧的版本。seaborn 0.13版本对numpy、pandas、matplotlib都有最低版本要求版本太旧时部分图形会运行报错或者行为诡异。我的建议是安装完成后跑一个冒烟测试pip install seaborn --upgrade python -c import seaborn; print(seaborn.__version__)然后随便加载一个内置数据集画一张histplot确认环境正常。如果是在离线内网环境就得预先下载好seaborn的whl包以及所有依赖的whl包。这个话题展开说能写一整篇这里只提醒一点如果你的matplotlib是用系统包管理器装的版本可能很旧seaborn画png没问题但某些字体和PDF导出功能会受限。6.2 0.13之后的objects接口声明式绘图的新选项seaborn 0.13开始引入了seaborn.objects接口这是一套全新的声明式绘图系统和之前所有函数式API都不同。传统写法是调用函数传数据调参数objects接口是声明一个Plot对象然后叠加图层import seaborn.objects as so ( so.Plot(tips, xtotal_bill, ytip) .add(so.Dot()) .add(so.Line(), so.PolyFit()) )这个接口的好处是代码更模块化统计变换和图形映射可以独立调整。比如上面这段先画散点再叠加多项式回归线。它适合做探索阶段的原型搭建改起来非常快。但它目前还比较新很多matplotlib层的高级定制仍需绕回底层所以我目前的生产级图还是以传统API为主objects只用在快速出草稿的场景。另一个常见的版本坑是seaborn中加载数据集需要联网。load_dataset第一次使用时会尝试从远程仓库下载数据文件离线环境会失败。遇到这种情况直接把数据集当作普通csv文件读进来就行函数只是帮你省了一步下载并不是唯一的数据入口。最后说点我的实际体会数据可视化这件事工具本身从来不是瓶颈真正难的是你想清楚这张图要回答什么问题。seaborn的价值在于它逼着你以数据为中心思考想拆分变量就用FacetGrid想比较分布就用小提琴加蜂群想看关系是否线性就画回归线和残差图。我从最初把seaborn当作高级matplotlib封装到后来真正理解它的网格和统计映射设计中间最大的转变就是不再纠结怎么把某张图画出来而是先问我需要展示数据的哪个侧面。按照这个思路去选图大部分情况下seaborn都会给你一个接近标准答案的选择。如果你读完这篇手痒想动手我建议直接拿一份你自己的真实数据按图形网格、分布密度、分类对比、回归诊断这个顺序逐层画一遍不用多久就能体会到这套工作流的顺手程度。
返回列表