06.Variation Partitioning方差分解

什么是方差分解

方差分解(Variation Partitioning)回答的问题是:

多组解释变量各自贡献了多少,哪些是独立贡献,哪些是共同解释?

普通 RDA 只能告诉你"所有变量一起解释了多少",无法区分气候和地理各自的贡献。方差分解把总解释量拆开。


原理

基于 RDA 的 Adj-R²,通过加减法计算各分区:

R² = 1 - SS_res / SS_total

SS_total = 所有品种、所有SNP频率偏离均值的平方和(数据本身有多散)
SS_res   = 模型拟合后剩余残差的平方和(模型没解释掉的部分)

残差 = 实际值 - 模型预测值(不是实际值减均值)

用 Adj-R² 而非 R²,是为了对变量数量做惩罚,避免加入无意义变量也能提升 R² 的问题。负值是正常现象,表示调整后解释量接近零。


分区含义(三组变量:气候/地理/海拔)

总遗传变异(100%)
├── [a] 纯气候        排除地理和海拔后,气候独立解释量
├── [b] 纯地理        排除气候和海拔后,地理独立解释量
├── [c] 纯海拔        排除气候和地理后,海拔独立解释量
├── [d] 气候∩地理     二者混杂,无法分离归因
├── [e] 气候∩海拔     二者混杂
├── [f] 地理∩海拔     二者混杂
├── [g] 三者共同      三者均混杂
└── [h] 残差          漂变、历史事件、未纳入变量

各分区计算方式(以 [a] 为例):

[a] = Adj-R²(气候+地理+海拔) - Adj-R²(地理+海拔)
    = 加入气候后,模型额外多解释了多少

本研究结果

数据:127个品种 × ~500万SNP,X1=32个气候变量(VIF筛选后),X2=经纬度,X3=海拔

vp <- varpart(freq_matrix_final,
              env_filtered[breeds_common, ],           # X1: 气候(32vars)
              env[breeds_common, c("Longitude_E","Latitude_N")],  # X2: 地理
              env_vars[breeds_common, elev_col, drop=FALSE])      # X3: 海拔

结果

分区 含义
[a] 纯气候 0.185 气候有独立贡献,GEA分析有意义
[b] 纯地理 0.010 IBD贡献极小,候选SNP不是地理假阳性
[c] 纯海拔 0.000 海拔效应完全被气候变量代理
[d] 气候∩地理 0.361 高度共线,全球尺度正常
[h] 残差 0.440 漂变、历史事件等无法解释的部分

核心结论

  1. 气候有真实的独立适应性驱动力([a]=18.5%)
  2. 中性地理隔离贡献极小([b]=1%),GEA候选SNP可信
  3. 海拔效应通过气候变量完全介导,无独立贡献([c]≈0)
  4. 气候和地理高度共线([d]=36.1%),全球尺度研究预期内

为什么海拔=0

海拔不直接施加选择压力,它通过创造低温、低氧、强辐射等环境来影响基因组。这些效应在32个气候变量里已经被完全覆盖,因此排除气候后,海拔没有独立的解释量。这也解释了为什么海拔在VIF过滤时就被剔除——与气候变量高度共线,信息冗余。


可视化代码

library(ggplot2)

theta <- seq(0, 2*pi, length.out=300)
r <- 1.3
c1 <- c(-0.9,  0.45); c2 <- c(0.9, 0.45); c3 <- c(0.0, -0.85)
circle1 <- data.frame(x=c1[1]+r*cos(theta), y=c1[2]+r*sin(theta))
circle2 <- data.frame(x=c2[1]+r*cos(theta), y=c2[2]+r*sin(theta))
circle3 <- data.frame(x=c3[1]+r*cos(theta), y=c3[2]+r*sin(theta))

ggplot() +
  geom_polygon(data=circle1, aes(x=x,y=y), fill="#378ADD", alpha=0.18, color="#185FA5", linewidth=0.8) +
  geom_polygon(data=circle2, aes(x=x,y=y), fill="#EF9F27", alpha=0.18, color="#BA7517", linewidth=0.8) +
  geom_polygon(data=circle3, aes(x=x,y=y), fill="#1D9E75", alpha=0.18, color="#0F6E56", linewidth=0.8) +
  annotate("text", x=-1.2,  y= 0.60, label="0.185", size=5.5, fontface="bold", color="#0C447C") +
  annotate("text", x=-1.2,  y= 0.35, label="[a] pure climate",    size=3.0, color="#185FA5") +
  annotate("text", x= 1.2,  y= 0.60, label="0.010", size=5.5, fontface="bold", color="#633806") +
  annotate("text", x= 1.2,  y= 0.35, label="[b] pure geography",  size=3.0, color="#854F0B") +
  annotate("text", x= 0.0,  y=-1.70, label="0.000", size=5.5, fontface="bold", color="#085041") +
  annotate("text", x= 0.0,  y=-1.95, label="[c] pure elevation",  size=3.0, color="#0F6E56") +
  annotate("text", x= 0.0,  y= 0.85, label="0.361", size=4.5, fontface="bold", color="#3C3489") +
  annotate("text", x= 0.0,  y= 0.62, label="[d] clim∩geo",        size=2.8, color="#534AB7") +
  annotate("text", x=-0.75, y=-0.65, label="0.000", size=4.0, fontface="bold", color="#3C3489") +
  annotate("text", x=-0.75, y=-0.88, label="[e] clim∩elev",       size=2.8, color="#534AB7") +
  annotate("text", x= 0.75, y=-0.65, label="0.008", size=4.0, fontface="bold", color="#3C3489") +
  annotate("text", x= 0.75, y=-0.88, label="[f] geo∩elev",        size=2.8, color="#534AB7") +
  annotate("text", x= 0.0,  y=-0.15, label="0.000", size=4.0, fontface="bold", color="#2C2C2A") +
  annotate("text", x= 0.0,  y=-0.38, label="[g] all three",       size=2.8, color="#444441") +
  annotate("text", x=-2.35, y= 0.55, label="Climate",   size=4.2, fontface="bold", color="#185FA5") +
  annotate("text", x=-2.35, y= 0.28, label="(32 vars)", size=3.3, color="#185FA5") +
  annotate("text", x= 2.35, y= 0.55, label="Geography", size=4.2, fontface="bold", color="#854F0B") +
  annotate("text", x= 2.35, y= 0.28, label="(lat+lon)", size=3.3, color="#854F0B") +
  annotate("text", x= 0.0,  y=-2.90, label="Elevation", size=4.2, fontface="bold", color="#0F6E56") +
  annotate("text", x=0, y=2.2, label="Residuals (unexplained) = 0.440", size=3.5, color="grey55") +
  coord_fixed(xlim=c(-3.0, 3.0), ylim=c(-3.2, 2.5)) +
  theme_void() +
  theme(plot.background=element_rect(fill="white", color=NA), plot.margin=margin(10,10,10,10))

# ggsave("varpart_3way.pdf", width=7, height=7)

Methods写法参考

We performed variation partitioning using the varpart function in the R package vegan to quantify the independent and shared contributions of climate (32 VIF-filtered variables), geography (latitude and longitude), and elevation to genomic variation. Pure climate explained 18.5% of genetic variation independently of geography and elevation ([a] = 0.185), while pure geographic effects accounted for only 1.0% ([b] = 0.010), indicating that climatic factors are the primary drivers of genomic differentiation. Elevation explained no independent genetic variation ([c] ≈ 0), suggesting its effects are fully mediated through climatic gradients.