审稿人如果盯着你的结果图问:“环境变量贡献多少?空间结构贡献多少?两者共同解释了多少?”你很难用一张普通柱状图把这一逻辑讲清楚。尤其在生态学、土壤学、地理学和环境科学论文里,“区位+环境”交互本来就常见,温度、降水与经纬度并不相互独立,直接比较单组变量的贡献很容易被质疑。很多作者这时候会主动放一张方差分解韦恩图:一个圈画环境变量,一个圈画空间变量,中间交叠区域表示共同贡献,旁边再留一个残差。期刊编辑喜欢这种图,因为它信息密度高、一眼能看出解释率怎么分账。
但你一旦在 Python 里搜索同类图,会发现生态学经典统计中常用的vegan::varpart()是 R 生态里的函数,Python 没有直接对应的标准库。网上关于韦恩图的代码不少,大多是展示给几组基因列表求交集,而不是把“回归模型的解释率”拆开算。要画期刊能用的“多面板方差分解韦恩图”,正确的拆法是:先用嵌套回归算清楚环境单独贡献、空间单独贡献、共同贡献和残差,再用matplotlib-venn把三块贡献画到子图里。
这篇文章不是教你怎么炫技,而是给你一套可以直接替换成自己数据的 Python 脚本。看完之后你能理解两个集合时的方差分区是怎么算出来的,也能用 2×2 多面板布局产出适合提交期刊的配图。文中的模拟数据只是用来演示方法,真实场景中你只需把 DataFrame 换成自己的响应变量和环境、空间变量即可。
1. 为什么期刊出图需要“方差分解韦恩图”
先看一个很常见的分析场景。你在某山区采集了样地数据,响应变量是土壤有机碳含量或物种多样性,解释变量分为两类:环境变量(温度、降水、pH、坡度)和空间变量(经纬度、海拔、距离邻体矩阵特征)。建模之后你发现整体模型解释率不错,但你还想知道:是环境筛选主导,还是空间扩散过程主导,两者又有多少是分不开的?
如果只画一张“三类环境变量重要度柱状图”,会出现一个理论问题:空间变量、环境变量本身在自然条件下常常存在空间自相关。例如温度和海拔强相关,海拔本身又是空间变量;降水受经纬度影响,经纬度本身也是空间变量。也就是说,两组解释变量之间不是正交的,简单比较“单组模型R²”会把共同解释部分重复计算。审稿人一旦抓住这一点,结论就可能被推翻。
方差分解的思路是主动把方差切割开,英文通常叫 Variation Partitioning 或 Variance Partitioning。在只考虑环境变量 E 和空间变量 S 时,它把响应变量的总变异拆成四份:
- E 单独解释的部分;
- S 单独解释的部分;
- E 和 S 共同解释、无法明确归因的部分;
- 残差,即模型没有解释的部分。
韦恩图之所以适合表达这个结果,是因为它天生就有“表示重叠”的结构。环境圈和空间圈分别代表各自引入的变异范围,两圆交叠正好表示共同解释。更重要的是,一个多面板韦恩图可以让不同响应变量、不同时期或不同生态区的分区结果横向并排,读者可以直观比较哪一类因子在哪个场景下更加主导。
Python 的方案闭环并不复杂。机器学习建模通常用R²看全模型解释能力,而方差分区的核心也只是“全模型R²”与“嵌套模型R²”之间的加减法。理解了这一步,剩下的事情就是绘图和排版。
2. 核心原理:方差分区计算并不神秘
为了让你后面读代码不卡壳,这里先把两个解释集时的计算逻辑讲清楚。假设环境变量集合为 E,空间变量集合为 S,响应变量为 Y。
先定义三个基本回归:
r2_E = R²(Y ~ E),即只用环境变量建模时的决定系数;r2_S = R²(Y ~ S),即只用空间变量建模时的决定系数;r2_all = R²(Y ~ E + S),即同时使用所有解释变量建模时的决定系数。
然后用下面的式子求分区:
| 分区 | 含义 | 计算方式 |
|---|---|---|
env_only | 环境变量的独立贡献 | r2_all - r2_S |
spa_only | 空间变量的独立贡献 | r2_all - r2_E |
shared | 环境与空间共同贡献 | r2_E + r2_S - r2_all |
residual | 无法解释的残差 | 1 - r2_all |
为什么环境单独贡献要用r2_all - r2_S?想象你把空间变量先放入模型,它已经解释了一部分变异;在此基础上再加入环境变量,模型解释率的上升值,就是环境变量在控制空间影响后仍然能额外解释的部分。现实中两组变量有相关,所以这一部分通常小于单组模型的R²。
shared的计算稍微绕一点,但逻辑同样清楚:把环境单模型与空间单模型解释率相加,如果两组变量完全正交,这个和就应该等于全模型解释率,意味着没有重叠;一旦因为相关重叠,r2_E + r2_S会大于r2_all,超出的部分就是它们共同分享的解释率。
四点分解有一个天然恒等式:
env_only + spa_only + shared + residual = 1这个等式非常方便,也是我们后面验证脚本是否正确的基础。需要说明,这里用的是模型在训练集上的R²。如果要提交严谨的方法学描述,你最好把高维小样本的问题纳入考虑:当变量个数增加时,普通R²会偏高,此时应使用 adjusted R² 或预留验证集。本文为了代码可读性,先以普通线性回归和R²示意,你在真正的论文分析中可以替换成 adjusted R²。
你可能会问,为什么这个话题放在“Python 机器学习”标签下?因为机器学习模型同样能输出 R²,理论上也能用这套嵌套思路做非线性版本的方差分解。但这里有一个现实坑:随机森林或梯度提升模型训练时会产生随机误差,OOB R² 并不能像线性回归那样精确地满足上面的恒等式,直接嵌套相减容易出现负分区。所以,如果审稿人对方法没有特殊要求,最稳妥的表达模型解释率的方案仍然是线性模型;机器学习特征重要性回答的是“哪些特征在预测中更关键”,它并不等价于方差分解对解释率的切割。
3. 适合用韦恩图方差分解的场景与避坑清单
一个多面板韦恩图看起来高级,但它并不是万能图。从实际处理经验和论文评审角度来看,下面几种情况适合用它:
- 解释变量可以明确归类为 2 组到 3 组,比如“环境 vs 空间”或“气候 vs 土壤 vs 空间”;
- 你的科学问题是相对贡献,而不是做高精度预测;
- 数据量和变量数基本匹配,没有在小样本上堆十几个变量的情况;
- 模型以线性关系为主,或你已经通过变量变换让变量关系接近线性。
不适合硬套韦恩图的情况也很清楚:
- 解释变量有几十个,无法归为少数几组,画出来的圆会失去意义;
- 变量集之间共线性极强,出现负分区时仍继续画图;
- 模型本质上使用深度神经网络或树模型,但方法学描述没有交代嵌套 R² 的合理性;
- 你只想做预测,不需要把各个变量集的贡献拆开解释。
另一个容易误用的点是“共享解释率”的概念。它并不表示环境变量和空间变量之间有什么因果机制,只表示在线性回归的意义上,两组变量所携带的信息有重叠,无法把重叠部分精确归给其中某一组。如果环境变量本身沿空间梯度分布,这个shared值会偏大,这是真实存在的现象,而不是代码 bug。
在软件实现层面也要有预期。R 里的vegan::varpart()同时输出多个复杂度指标,Python 没有现成等价包;但好消息是,两集分区需要的回归运算非常简单,自己实现其实是可控的。真正难的是图面工程:每个子图的圆圈布局、百分比文本、残差的位置、面板编号、保存格式。这也是后面代码重点解决的问题。
4. 环境准备与数据组织
做这个分析不需要安装重型环境。如果你的机器上已经能跑 pandas 和 scikit-learn,基本就够了。建议环境如下:
- Python 3.8 及以上;
- numpy