7.1 FacetGrid 与子图布局


文档摘要

7.1 FacetGrid 与子图布局 FacetGrid 是所有分面工厂函数的地基:初始化时声明数据与分面变量,再用 map 方法把任意绘图函数填进每个面板。手动装配它,等于拿到分面布局的完整控制权。 第 3.3 节用了 relplot 的分面,第 4.3 节用了 catplot 的分面——本节终于打开这两个工厂函数的引擎盖。看懂 FacetGrid 之后,工厂函数从"魔法"变成"语法糖"。 手动装配四步 map 的机制值得细看:它按分面变量把数据切片,对每片调用传入的函数,并把 map 后面的位置参数作为该函数的列名参数。 等价于对每个餐段各执行一次 。

7.1 FacetGrid 与子图布局

FacetGrid 是所有分面工厂函数的地基:初始化时声明数据与分面变量,再用 map 方法把任意绘图函数填进每个面板。手动装配它,等于拿到分面布局的完整控制权。

第 3.3 节用了 relplot 的分面,第 4.3 节用了 catplot 的分面——本节终于打开这两个工厂函数的引擎盖。看懂 FacetGrid 之后,工厂函数从"魔法"变成"语法糖"。

手动装配四步

import seaborn as sns import matplotlib.pyplot as plt tips = sns.load_dataset('tips') # 第一步:声明数据与分面变量,网格此刻已建立 g = sns.FacetGrid(data=tips, col='time', height=3.5) # 第二步:map 把绘图函数填进每个面板 g.map(sns.histplot, 'total_bill', binwidth=5) # 第三步:批量打磨 g.set_axis_labels('总消费(美元)', '观测数') g.set_titles('{col_name}时段')

map 的机制值得细看:它按分面变量把数据切片,对每片调用传入的函数,并把 map 后面的位置参数作为该函数的列名参数。g.map(sns.histplot, 'total_bill') 等价于对每个餐段各执行一次 sns.histplot(x=切片的 total_bill)。任何接受裸列名的绘图函数都能被 map——包括你自己写的函数:

# 第四步进阶:map 自定义函数(加中位数参考线的直方图) def hist_with_median(**kwargs): sns.histplot(**kwargs) plt.axvline(kwargs['data'].iloc[:, 0].median(), color='crimson', linestyle=':') g = sns.FacetGrid(data=tips, col='day', col_wrap=2, height=3) g.map_dataframe(hist_with_median, x='total_bill') g.set_axis_labels('总消费(美元)', '观测数') # map_dataframe 与 map 的差别:前者传整个 DataFrame 切片进函数(支持 data= 形态), # 后者只传列。需要 kwargs['data'] 时必须用 map_dataframe

自定义函数的唯一约定:接受关键字参数(hue 与 data 会自动传进来),在里面正常调 seaborn 或 matplotlib。这是把任意图形逻辑装进分面网格的标准插槽。

工厂函数替你做了什么

# relplot 的等价手动实现 g = sns.FacetGrid(data=tips, col='time', hue='smoker', height=3.5) g.map_dataframe(sns.scatterplot, x='total_bill', y='tip') g.add_legend(title='吸烟区') # 对比:sns.relplot(data=tips, x='total_bill', y='tip', col='time', hue='smoker') # 输出说明:两者成图一致。工厂函数封装的正是这三步—— # 建 Grid、按 kind 填图、收尾加图例

下探到 FacetGrid 的收益在工厂函数够不着的场合:想让 add_legend 的标题带换行、想对特定面板单独缩放、想在 map 之外做数据变换(先对每片做标准化再画),这些需求在 relplot 的参数表里都找不到位置,在手动装配里都是普通 Python。

轴共享与网格控制

# sharex / sharey:False 让各面板独立刻度 g = sns.FacetGrid(data=tips, col='day', height=3, sharey=False, despine=True) # despine 直接在构造器里做 g.map(sns.histplot, 'total_bill') # 面板顺序:分面变量转有序 category 控制 order = ['Thur', 'Fri', 'Sat', 'Sun'] g = sns.FacetGrid(data=tips, col='day', col_order=order, height=3) g.map(sns.histplot, 'total_bill') # 输出说明:col_order 参数直接控制面板排列,比转 category 更轻量

输出说明:col_order 参数直接控制面板排列,比转 category 更轻量

案例:按分面标准化的对比图

背景:四个季度的日销量要做形态对比,但各季度总量差异大,直接分面画直方图会被总量差异淹没形态差异。

操作

import numpy as np import pandas as pd rng = np.random.default_rng(5) days = pd.date_range('2025-01-01', '2025-12-31', freq='D') df = pd.DataFrame({ 'date': days, 'quarter': 'Q' + ((days.quarter).astype(str)), 'sales': rng.gamma(2, 100, len(days)) * (1 + 0.3 * np.sin(days.dayofyear / 58)), }) df['sales_z'] = df.groupby('quarter')['sales'].transform( lambda s: (s - s.mean()) / s.std()) # 组内标准化:只留形态 g = sns.FacetGrid(data=df, col='quarter', height=2.8, despine=True) g.map_dataframe(sns.kdeplot, x='sales_z', fill=True, color='#4c78a8') g.set_axis_labels('组内标准化的销量', '密度') g.set_titles('{col_name} 形态')

结果解读:四个面板的密度曲线形态可以直接对比——Q1 与 Q3 明显右偏,Q2 接近对称。组内标准化在数据层完成后,分面层一行不用改;这正是"数据加工与图形装配正交"的又一次体现。变式:把 kdeplot 换成自定义函数(叠中位数竖线),map_dataframe 的插槽设计让替换零成本。

⚠️ 常见坑:map 与 map_dataframe 用错时最常见的报错是函数收到了 Series 却按 DataFrame 取列。约定记死一条:自定义函数一律写 def f(**kwargs) 并用 map_dataframe,兼容性最好。

💡 关键直觉:工厂函数是自动挡,FacetGrid 是手动挡——会开手动挡的人不是每次都开,但知道引擎在哪,自动挡坏不了的时候不会束手无策。

本节要点回顾

  • 四步装配:建 Grid、map 填图、批量打磨、按需 add_legend;
  • map_dataframe 是自定义插槽:传整片 DataFrame,接 data= 形态的函数;
  • 工厂函数是语法糖:relplot 三步封装,参数表没有的需求下探解决;
  • col_order 轻量控序:面板顺序不用回数据层转 category;
  • 组内标准化在数据层做:形态对比的标准预处理,图形层零改动。

下一节先解决"数据大到画不动"的问题,再回来拆另外两个 Grid。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U