ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

R语言群落生态学统计建模全流程:多样性、排序与可视化指南

R语言群落生态学统计建模全流程:多样性、排序与可视化指南

做群落生态学的数据分析,我见过太多人在拿到数据后卡在同一个地方:算完多样性指数不知道下一步该做什么,或者跑完排序图不知道怎么美化才能放进论文里。这篇文章我会用一套完整可跑的R代码,带你走完群落生态学统计建模的全流程——从物种丰度表出发,经过数据转换、多样性计算、差异检验、排序分析,一直到出版级可视化的最终呈现。所有代码基于我实际项目中最常用、最稳定的方案组合,不堆砌花哨包,只讲能直接复现的东西。

能解决什么问题?如果你正在处理植被样方数据、土壤动物调查数据或微生物群落数据,并且需要回答这三类问题——"样方之间物种组成有没有显著差异""环境因子如何驱动群落结构变化""怎样用一张图把群落差异讲清楚",那这份指南就是为你准备的。内容适配生态学本科论文、硕士课题以及科研项目前期的探索性分析场景。

我建议你按顺序阅读:数据塑形 → 多样性分析 → 差异检验 → 排序分析 → 绘图输出。每一环节的代码逻辑我会拆开讲清楚,你对照着自己的数据替换列名就能用。

1. 数据准备与预处理:R里面最容易被忽略的第一道坎

1.1 整理"样方-物种-环境"三件套:标准数据结构的建立

绝大多数生态学数据最终都能拆成三张表:物种丰度表、样方环境因子表、物种属性表(可选)。先建立一个清晰的"数据框解决方案",后面所有的分析都围绕这三张表展开,这样至少能省下四成不必要的返工。

先构造一套模拟数据,便于你理解结构并直接跑通流程:

# 设定随机种子便于复现 set.seed(42) # 创建一个模拟的物种丰度表:24个样方,15个物种 sites <- paste0("Site", 1:24) species <- paste0("Sp", 1:15) # 模拟两个处理组:对照(Control)与处理(Treatment) group_info <- data.frame( site = sites, group = rep(c("Control", "Treatment"), each = 12), pH = round(runif(24, 5.5, 8.5), 2), moisture = round(runif(24, 20, 80), 1), nitrogen = round(runif(24, 10, 50), 1) ) # 构造丰度数据:使用负二项分布模拟生态学中常见的过度离散数据 abundance_mat <- matrix(0, nrow = 24, ncol = 15) colnames(abundance_mat) <- species for (i in 1:24) { base_lambda <- ifelse(group_info$group[i] == "Treatment", 30, 15) for (j in 1:15) { if (runif(1) > 0.25) { # 25%的零丰度,模拟真实群落 abundance_mat[i, j] <- rnbinom(1, mu = base_lambda * runif(1, 0.1, 2), size = 2) } } } # 合成丰度表 abundance_table <- as.data.frame(abundance_mat) abundance_table$site <- sites abundance_table <- abundance_table[, c("site", species)]

这里有一个操作层面的关键点:物种丰度表格的第一列固定为样方编号,从第二列开始才是物种丰度。之所以强制这样设计,是因为后续绝大部分R包(vegan、ggplot2、dplyr)在合并数据时都要用到这一列来做关联键。不少人在数据整理阶段没有规范列名,结果做NMDS的矩阵旋转时经常报错。

1.2 数据标准化:切切实实影响结果的一步

群落生态学的原始丰度数据有两个显著特征:数值跨度大(某个优势种可能上百,稀有种只有个位数)和包含较多零值。如果不做处理直接扔进距离算法,优势种会完全主导分析结果,稀有种的贡献会被压制到忽略不计。

这里我建议用hellinger转化而不是常规的log(1+x)。hellinger转化同时完成了"消除量纲差异"和"降低零值影响"两件事,而且对后续的PCA、RDA等线性方法最为友好,因为它能保证转化后的欧氏距离与弦距离有很好的数学性质。

library(vegan) library(dplyr) library(tidyr) # 提取丰度矩阵(去掉样方列) mat <- abundance_table[, -1] # Hellinger转化,注意需要先转置,把样方放在行上 mat_hel <- decostand(mat, method = "hellinger") rownames(mat_hel) <- abundance_table$site # 为后续方便,转回长格式做可视化 abundance_long <- abundance_table %>% pivot_longer(cols = -site, names_to = "species", values_to = "abundance")

提示:vegan包中的decostand()标准化方法远不止hellinger一种。常用的还有total(百分比)、max(最大值标准化)、pa(0/1出现与否)。我的经验是:检测排序用hellinger,单纯做热图展示可以先做total,差异检验用原始丰度更合适。没有哪种方法是绝对正确的,关键取决于你回答什么问题。

2. Alpha多样性计算与组间差异检验:从香农指数到可视化

2.1 三个核心指数和它们的R实现

Alpha多样性是群落生态学论文里最常出现的指标。它衡量的是单个样方内部物种的丰富度与均匀度。常见的指数包含:Observed(物种数)、Shannon(香农指数)、Simpson(辛普森指数)。这三个指数各有侧重:Observed只考虑物种数量,Shannon同时考虑丰富度和均匀度,Simpson则对优势种更敏感。

实际操作中我通常会用vegan::diversity()配合specnumber()一次性算出多个指数。

# 计算Alpha多样性指数 shannon <- diversity(mat, index = "shannon") simpson <- diversity(mat, index = "simpson") observed <- specnumber(mat) # 整合到样方信息框 alpha_div <- group_info %>% mutate(shannon = shannon, simpson = simpson, observed = observed) head(alpha_div)

这步得到的alpha_div就是后续所有Alpha多样性分析的"主数据框"。我习惯把环境因子、分组信息、多样性指数全部放进同一个数据框,后续绘图时用ggplot2映射变量会格外顺手,不需要反复合并。

2.2 用箱线图对比处理组差异

拿到多样性指数后,你一定会画箱线图对比Control和Treatment组的差异。这里推荐一个十分常见的组合:箱线图+散点叠加+jitter抖动,既展示分布形态,又暴露每个样方的具体位置。

library(ggplot2) library(ggpubr) # 用ggpubr的compare_means快速计算检验结果 shannon_plot <- ggplot(alpha_div, aes(x = group, y = shannon, fill = group)) + geom_boxplot(width = 0.5, alpha = 0.7, outlier.shape = NA) + geom_jitter(width = 0.15, size = 1.5, alpha = 0.8, color = "grey30") + stat_compare_means(method = "wilcox.test", label = "p.signif", label.x = 1.5) + scale_fill_manual(values = c("Control" = "#88A9C6", "Treatment" = "#C6A888")) + labs(x = "Group", y = "Shannon Index") + theme_bw(base_size = 14) + theme(legend.position = "none", panel.grid.major = element_blank(), panel.grid.minor = element_blank()) shannon_plot

这段代码的重点在于stat_compare_means(),它会在箱线图上方自动标注Wilcoxon检验的显著性符号(ns、、、)。如果样本量够大,可以换成method = "t.test"。但生态学调查数据往往样本量小、不满足正态性,所以在多数情况下我用Wilcoxon秩和检验而非t检验。

2.3 当你有三个以上分组时:Kruskal-Wallis检验与事后比较

如果实验设计是三个以上组别(比如不同海拔梯度、不同干扰强度),那么两两Wilcoxon检验会带来多重比较的p值膨胀问题。这时候的做法是:先做Kruskal-Wallis检验看整体差异,再做Dunn事后检验确定哪些组之间存在显著差异。

library(FSA) # 假设有个三水平的landuse变量 alpha_div$landuse <- rep(c("Forest", "Grassland", "Cropland"), each = 8) # Kruskal-Wallis检验 kruskal.test(shannon ~ landuse, data = alpha_div) # Dunn事后检验(FSA包),用BH法校正p值 dunn_result <- dunnTest(shannon ~ landuse, data = alpha_div, method = "bh") dunn_result$res

注意:不要直接对同一个数据反复做两两t检验。我在实际审阅中多次发现有人根本不做校正直接比较,这会导致假阳性率飙升。三种处理组各8个重复的小样本研究,不做校正就两两比较是相当危险的。

2.4 Alpha多样性的可视化进阶:多样性稀释曲线

除了箱线图,**稀释曲线(rarefaction curve)**是Alpha多样性展示中很有说服力的图件。它反映的是当测序/调查深度不断增加时,新物种被发现的速度。曲线趋于平坦,说明采样量充分;曲线仍然陡峭,说明采样不足。

# 计算稀释曲线 rare_curve <- rarecurve(mat, step = 20, lwd = 2, col = ifelse(group_info$group == "Control", "#88A9C6", "#C6A888"), ylab = "Species Richness", xlab = "Sample Size") # 手动添加图例 legend("bottomright", legend = c("Control", "Treatment"), col = c("#88A9C6", "#C6A888"), lty = 1, lwd = 2, bty = "n")

这类图形在审稿人那里印象分很高,因为它直接说明了采样深度是否足够支撑你的结论。不过要留意:如果你的样方之间总丰度差异很大,稀释曲线会变得一团乱麻,此时可以考虑用rarecurve的tidy = TRUE参数将结果导出后重新用ggplot2绘制,可以按组做均值曲线。

3. Beta多样性分析:群落差异的灵魂——距离矩阵与PERMANOVA

3.1 距离算法的选择逻辑:Bray-Curtis为什么是默认选项

Beta多样性回答的核心问题是"样方之间的物种组成有多大差异"。这需要先计算样方两两之间的距离矩阵。生态学中最常用的距离指标是Bray-Curtis距离,它的计算公式基于丰度绝对差值和之和的比,对零值没有那么敏感,而且对同一群落中的优势种和稀有种均能保持稳定的评估。

相比欧氏距离,Bray-Curtis直接作用于原始丰度数据而不需要先标准化,它天然处理了"两个样方共享物种越多、共有多度越高,距离越近"的生态直觉。我自己做分析时,90%以上的Beta多样性场景都直接用Bray-Curtis。

# 计算Bray-Curtis距离矩阵 bc_dist <- vegdist(mat, method = "bray")

如果需要同时考虑物种有无和丰度差异的平衡,可以尝试vegdist(mat, method = "jaccard")中针对二元数据的Jaccard指数。但如果你手头是丰度数据而不是出现/不出现数据,我更推荐直接使用Bray-Curtis。

3.2 PERMANOVA检验:组间差异显著性的标准答案

拿到距离矩阵后,怎么检验组间差异是否显著?经典的做法是PERMANOVA(置换多元方差分析),对应vegan包中的adonis2()函数。它通过置换检验来判断组间距离是否显著高于组内距离,既不要求数据正态,也不要求方差齐性,非常适合生态学数据的特征。

# PERMANOVA分析——需要将分组信息与矩阵行对齐 perm_result <- adonis2(bc_dist ~ group, data = group_info, permutations = 999) perm_result # 如果是多因素设计,可以加入协变量 perm_result2 <- adonis2(bc_dist ~ group + pH + moisture, data = group_info, permutations = 999, by = "terms")

adonis2()的输出中通常包括:F值、R2(组间变异解释比例)和Pr(>F)。注意R2这个指标极其有用,它告诉你"分组"这一因素究竟解释了群落差异的多大比例。在实际操作中,即使p值显著,如果R2只有0.05,那么说明分组虽然有统计学意义,但实际解释力有限,讨论时需要格外谨慎。

3.3 组内离散度检验:PERMANOVA被忽略的重要前提

使用PERMANOVA时必须检查一个前置条件:组间离散度是否一致。因为PERMANOVA对"组内方差齐性"并非完全免疫,如果一组内部差异极大而另一组内部非常集中,p值可能出现虚假的显著结果。此时需要配合betadisper()来检验离散度。

# 组内离散度检验 dispersion <- betadisper(bc_dist, group = group_info$group) permutest(dispersion) # 如果离散度差异显著,说明PERMANOVA结果需要谨慎解释 plot(dispersion)

我在实际项目中经常遇到的情况是:处理组的样方因为边缘效应导致组内异质性远超对照组,PERMANOVA得出p<0.01,但betadisper的检验同样显著。这时严谨的做法不是在文章里删掉这个检验,而是直接说明"群落组成的显著差异有一部分可能来自组内变异的不对称性",并在后续分析中用NMDS图或MDS排序图来直观呈现差异模式。

4. 群落排序分析:从PCA到NMDS再到RDA的完整决策链

4.1 先搞清楚三种排序方法的使用边界

排序分析是群落生态学中最容易让人困惑的部分。我每次给课题组新同学讲,都要先强调一个原则:没有最好的排序方法,只有最匹配数据结构和科学问题的排序方法。

  • PCA(主成分分析):基于欧氏距离,适用于线性响应关系,对数据标准化有较高要求。如果你的数据量纲差异大,基本不直接用原始丰度。
  • RDA(冗余分析):有约束的线性排序,适合"物种多度矩阵与环境因子矩阵"之间的线性关系分析。
  • CCA(典范对应分析):基于单峰响应模型,适合环境梯度跨度很大的情况(比如从干旱区到湿润区的样带调查)。
  • NMDS(非度量多维尺度分析):基于排序等级信息而非原始距离值,对数据分布几乎没有假设,是目前生态学论文里最稳妥的beta多样性可视化方案。

对于最常见的"样方不多、环境梯度未知、只是想看组间差异是否清楚"的场景,我的默认推荐是:NMDS做展示,PERMANOVA做检验,两者结合使用。

4.2 NMDS完整实操:从距离矩阵到ggplot2出图

NMDS的核心思路是把高维距离矩阵降维到二维或三维空间,同时尽量保持样方之间的相对距离顺序。它可以基于任何距离矩阵,这也是它为什么如此灵活的原因——你可以直接对Bray-Curtis距离矩阵做NMDS。

# NMDS排序,尝试二维解 nmds_result <- metaMDS(bc_dist, k = 2, trymax = 100, trace = FALSE) # 查看压力系数(stress) nmds_result$stress

stress值是个重要参考指标:小于0.1说明排序效果优秀,0.1-0.2说明可以接受,大于0.2则不太能说明问题。如果stress过大,可以尝试增加维度k=3,但展示上会困难,所以建议先在原始数据层面检查是否存在一个或两个离群样方。

提取出样方坐标后就可以绘图:

# 提取NMDS坐标 nmds_scores <- as.data.frame(scores(nmds_result)$sites) nmds_scores$site <- rownames(nmds_scores) nmds_scores <- nmds_scores %>% left_join(group_info, by = "site") # 绘制NMDS排序图 nmds_plot <- ggplot(nmds_scores, aes(x = NMDS1, y = NMDS2, color = group)) + geom_point(size = 3, alpha = 0.85) + stat_ellipse(aes(fill = group), alpha = 0.15, geom = "polygon", level = 0.95) + scale_color_manual(values = c("Control" = "#88A9C6", "Treatment" = "#C6A888")) + scale_fill_manual(values = c("Control" = "#88A9C6", "Treatment" = "#C6A888")) + theme_bw(base_size = 14) + labs(x = "NMDS1", y = "NMDS2") + theme(panel.grid = element_blank()) nmds_plot

这里stat_ellipse()画的是95%置信椭圆,能非常直观地展示两组样方在排序空间中的分离程度。我见过太多人不用置信椭圆,只散点叠加,结果视觉上根本分不清组间差异。加上椭圆之后,哪怕p值并不显著,你也能一眼看出组间是否存在趋势性分离。

4.3 环境因子驱动分析:RDA的基本框架与变差分解

如果科学问题是"哪些环境因子显著解释了群落组成变化",就需要做带约束的排序分析。在梯度不极端、物种响应以线性为主的应用场景中,RDA是最实用的选择。

# 以hellinger转化后的丰度矩阵为响应变量 # 以标准化后的环境因子作为解释变量 env_data <- group_info %>% dplyr::select(pH, moisture, nitrogen) # 对解释变量做标准化 env_scaled <- as.data.frame(scale(env_data)) rownames(env_scaled) <- group_info$site # RDA分析 rda_result <- rda(mat_hel ~ pH + moisture + nitrogen, data = env_scaled) # 查看结果摘要 summary(rda_result) # 方差膨胀因子检查共线性 vif.cca(rda_result)

注意:vif.cca这个步骤很容易被跳过,但它在多环境因子的分析中是极其关键的。如果某个环境因子的VIF > 10,说明这个因子和其他因子高度共线,RDA中它的效应无法被可靠解释。此时应该剔除其中一个因子,重新拟合模型。

RDA分析结果中,你会看到每个环境因子的条件效应(即排除其他因子后该因子的独立解释率)和显著性。判断显著性的方式是anova()函数做置换检验:

# 整体模型的显著性 anova(rda_result, permutations = 999) # 每个环境因子的显著性(按项检验) anova(rda_result, by = "terms", permutations = 999)

一个实用的结果呈现方式是做一个变差分解(variance partitioning),将"环境因子所能解释的群落变异"继续细分到各组因子中。在生态学研究中,这能清晰地回答"土壤因子重要还是水分因子重要"这类问题。

# 变差分解:以pH、水分和氮素为两块解释变量组 # 假设这两个分组:土壤理化(pH + nitrogen) vs 水分(moisture) rda_part <- varpart(mat_hel, ~ pH + nitrogen, ~ moisture, data = env_scaled) plot(rda_part)

5. 出版级绘图与结果输出:一套代码搞定论文排版

5.1 使用patchwork组合图形,一次输出多面板图

生态学论文中大量使用多面板组合图(比如Fig.1排列Alpha多样性箱线图+NMDS排序图+RDA排序图)。这需要把前面积累的ggplot2对象组合起来。我这里通常用patchwork包,它比gridExtra更简洁,语法直观,现场讲解时也容易懂。

library(patchwork) # 将shannon_plot和nmds_plot拼接成两栏图 combined_plot <- shannon_plot + nmds_plot + plot_layout(ncol = 2, widths = c(1, 1.5)) + plot_annotation(tag_levels = 'A') # 保存为高分辨率图,符合期刊最低要求(300 dpi) ggsave("figure_combined.png", combined_plot, width = 10, height = 4.5, dpi = 300, bg = "white")

这里plot_annotation(tag_levels = 'A')会自动生成"图A""图B"的标签。很多初学者用PPT拼图,不仅效率低,还会因为画幅比例不一致导致输出图件分辨率不统一。使用patchwork直接输出,整张图的比例和字体都能保持一致。

5.2 排序图的物件美学:多元ggplot2技巧沉淀

如果想让排序图达到"投稿级"而不是仅仅"能看",下面几个细节非常值得留意:

  • 箭头与环境因子:RDA或CCA中环境因子是以箭头形式呈现的,需要用geom_segment()添加;如果直接使用plot(rda_result),得到的图件是base R风格,与ggplot2的排版风格难以融合。我建议用vegan::scores()提取RDA坐标后用ggplot2重绘。
  • 物种点过密时的处理:如果你的物种数量超过30个,图形会非常拥挤。此时可以用geom_text()加check_overlap = TRUE避免标签重叠,或者只绘制对排序轴贡献最大的前10个物种。
  • 坐标轴比例:NMDS轴的默认比例不是1:1,为了让图形更具可读性,可以用coord_fixed()固定坐标轴比例,保证椭圆和点之间的关系不变形。

5.3 将全部结果导出为结构化表格

分析做完后,把关键数值结果导出成csv格式非常必要。这里提供一个快速导出工作流:

# 将多样性指数和PERMANOVA结果写入一个汇总表 write.csv(alpha_div, "alpha_diversity_results.csv", row.names = FALSE) # 把PERMANOVA结果转成数据框再导出 perm_df <- as.data.frame(perm_result) perm_df$term <- rownames(perm_df) write.csv(perm_df, "permanova_results.csv", row.names = FALSE) # 保存RDA中每个环境因子的显著性 rda_anova <- as.data.frame(anova(rda_result, by = "terms", permutations = 999)) write.csv(rda_anova, "rda_env_significance.csv", row.names = TRUE)

写论文时,用rmarkdown可以直接引用这些csv,数据整理和写作联动,能节约大量后期手工填写表格的时间。

6. 我在实际项目中积累的五个经验与常见坑

6.1 关于样方数量:PERMANOVA不是万能药,样本量依然是硬约束

如果每个组只有3个样方,PERMANOVA得到的p值其实可重复性很差。即使置换次数999次,可产生的独特置换数量也只有10种上下,p值的分辨率极其有限。我建议在实验设计阶段就尽可能把每组样方控制在5个以上。做微生物扩增子测序时,每个处理组6-8个生物学重复是比较稳妥的下限。

6.2 关于零值占比:过高的零值会让距离矩阵信息量骤减

如果你的物种丰度表超过70%都是零值,那么Bray-Curtis矩阵中会出现大量"完全相同"的样方对,距离为0。这会让NMDS排序变得不稳定。解决思路之一是使用wisconsin标准化或者先做decostand(..., method = "pa")再结合Jaccard距离,看哪种方法能更好地区分组间差异。不要回避这个问题,它直接影响你整个Beta多样性分析链条的可靠性。

6.3 关于数据泄露:环境因子和物种数据一定要分开存储

我曾经见过一个团队把环境因子记录在物种丰度表右侧的若干列中,结果在做NMDS时,环境因子列被当成了"物种",得到的结果完全无法解释。遇到这种情况需要立刻做出规范化的数据结构三表分离——这不仅是操作便利的问题,也是分析结果正确性的底线。

6.4 关于图形中的颜色方案:色盲友好远比花哨重要

生态学期刊越来越注重图表对色盲读者的可访问性。不要使用红绿对比作为唯一的区分方式。我常用的方案是蓝/橙组合("#88A9C6"与"#C6A888"),不仅色盲友好,而且黑白打印时灰度层次依然清晰。这条建议听起来简单,但绝大多数初稿图都在这里栽过跟头。

6.5 关于R版本和包版本:锁定环境是复现的前提

vegan包在不同版本之间的函数参数有变动(尤其是adonis的用法),ggplot2的美学默认值也在历次更新中有所调整。建议每做一个项目就通过renv::snapshot()锁定包版本,或者在方法部分明确写出所使用包的具体版本号。我自己由于早期没有锁版本,曾在半年后重新跑同一个脚本时因为adonis到adonis2的迁移而耗费不少时间排查。现在项目一开始就锁环境。

7. 额外补充:对"群落生态学统计建模"的下一步扩展方向

上面这套流程是群落生态学分析的"地基"。如果读完并顺利跑通了这一切,下一步可以考虑向这两个方向扩展:

  • 零膨胀模型:如果样方中有大量零值,并且你关心的是单个物种多度如何受环境驱动,glmmTMB包中的零膨胀负二项模型会是一个更好的选择。它把"是否存在"和"有多少"两个过程分别建模,能更精确地刻画物种分布。
  • 系统发育群落生态学:结合物种系统发育树计算系统发育多样性(PD)、系统发育信号等指标。这类分析对生态位分化、群落构建机制的研究更有解释力。

不过,在深入那些进阶方法之前,把数据预处理、多样性计算、差异检验、排序分析和可视化这个完整闭环跑通,是学习效率和产出可靠性最高的一条路径。希望这份指南能帮你省下来回调试脚本、反复查阅文档的时间,直接解决群落生态学统计分析中最核心的问题。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进