☰
R语言生态数据工作流:从多样性分析到空间建模
2026/10/16 7:21:50 网站建设 项目流程

做生态数据分析,绕不开R语言。这倒不是因为它在编程语言里最时髦,而是生态问题很少有单一公式能闭环解决:一份物种多度矩阵要先算多样性指标,再做排序看清群落结构,最后还要结合环境栅格把格局投射到空间上。这条流水线里,统计分析、排序、空间建模三个环节缺一不可,而R恰好能把vegan、ggplot2、sf、terra这些各管一摊的包串成一条完整的链路。这篇文章我会用一个虚构但足够完整的样方调查项目来演示整套流程,适合刚开始接触生态数据科学、想系统上手R语言的人,也适合已经会跑vegan基础函数、但还没打通"统计—排序—空间"全链条的研究者。

整个演示围绕"模拟项目X"展开:某保护区布了20个10米×10米样方,调查了45个物种的多度,记录了海拔、土壤pH、有机质含量和林冠盖度四个环境因子,每个样方还带投影坐标和生境分组。所有代码都是我在实际项目里反复用过的常规做法,你可以直接按这个框架往自己的数据上套。

1. 从调查表到数据框:数据形态决定分析路径

很多生态分析翻车,不是统计方法不会,而是数据形态一开始就错了。生态调查产生的原始数据大致有三种形态,每一种对应着不同的分析路径,必须先分清楚。

1.1 生态数据的三种形态

第一种是物种×样方矩阵,行是样方,列是物种,格子里填多度、盖度或者有/无。这个矩阵是群落分析的核心输入,多样性指数、排序、群落差异检验全都围绕它展开。

第二种是环境变量表,每一行对应一个样方,记录海拔、温度、土壤pH、有效养分、干扰等级这类解释性变量。它单独拿出来可以做环境梯度分析,和物种矩阵合在一起就能做约束排序、方差分解、物种分布模型。

第三种是空间数据,包括样方的经纬度或投影坐标,以及覆盖整个研究区域的连续环境栅格图层。空间数据负责回答"格局在哪里、怎么向未采样区域外推"的问题。

这三类数据的组织好坏,直接决定后面分析能不能顺畅跑通。我在接手过某开发者的半成品数据时深有体会:Excel里一堆合并单元格和手动筛选痕迹,导致方差分析的结果跟原始记录完全对不上,最后只能重新回查原始调查表。

1.2 一张干净的样方表长什么样

先约定好编码规则。物种表用宽格式:一个样方占一行,一个物种占一列,样方ID放在第一列。环境表也用宽格式:样方ID、分组变量、环境因子、坐标各占一列。这两张表通过唯一的样方ID关联。

library(tidyverse) library(vegan) # 物种表:行=样方,列=物种,值=多度(株数或盖度%) species_raw <- read_csv("data/raw/species_matrix.csv") species_matrix <- species_raw %>% column_to_rownames("plot_id") # 环境表:每行一个样方,含分组、环境因子、投影坐标 env_data <- read_csv("data/raw/env_data.csv") str(env_data)

这里有几个容易踩的细节。第一,物种名称列必须是合法且唯一的,中文名里的全角括号、空格、特殊符号都会在后续绘图和排序时报错。第二,多度和盖度不要混在同一张表里,数值含义不同,算法对它们的响应也不同。第三,缺失值NA和真实的0是完全不同的含义,多度表里没记录的物种应该是0而不是NA,否则vegdist()计算距离时会出问题。

1.3 用脚本而不是在Excel里手搓

生态数据科学的核心是可复现性。你两个月后回看自己的分析,或者同事接手你的数据,都需要能完完整整重跑一遍。R脚本本身就是在记录整个分析过程:数据从哪来、每一步做了什么处理、用了什么参数。

我用下来的习惯是:原始数据永远只读不写,所有清洗和计算都通过脚本生成中间文件。这样即使某个步骤出问题,也能快速定位是哪一行代码导致的,而不需要在Excel的撤销记录里考古。

2. 统计分析实战:多样性指数、稀疏化与群落差异检验

统计分析是生态数据科学的起点。拿到物种矩阵后,第一件事通常是量化群落的多样性,然后比较不同生境或处理之间的差异。

2.1 三个经典指数各有脾气:Shannon、Simpson与均匀度

最常用的是物种丰富度、Shannon-Wiener指数和Simpson指数。丰富度就是样方里出现的物种数,直观但容易受采样强度影响。Shannon指数H' = -Σ pi·ln(pi),其中pi是第i个物种的相对多度,它对稀有种比较敏感,群落里出现新物种时会有明显反应。Simpson指数的常见形式是1 - Σ pi²,它更强调优势种的分量,反映的是"随机抽两个个体属于不同物种"的概率,数值上对群落优势化很敏感。

在vegan里计算这些指数就是几行代码的事:

richness <- specnumber(species_matrix) shannon <- diversity(species_matrix, index = "shannon") simpson <- diversity(species_matrix, index = "simpson") evenness <- shannon / log(richness) # Pielou均匀度 div_df <- tibble( plot_id = rownames(species_matrix), richness, shannon, simpson, evenness ) %>% left_join(env_data, by = "plot_id")

实操建议是:报告里通常给出丰富度加Shannon再加Pielou均匀度,如果群落优势现象非常明显,比如某个物种占了七八成多度,再补一个Simpson指数。不要为了凑数把一堆高度相关的指数全列出来,审稿人看到冗余指标反而会追问它们之间的自相关性。

2.2 稀疏曲线:样本量不一样时,别直接比物种数

直接拿样方里的物种数做比较,有一个前提:所有样方的采样强度一致。现实里这很难保证,样方大小不同、鉴定时间不同、样方里个体密度不同都会导致丰富度虚高或虚低。

解决办法是做稀疏化。用iNEXT或者vegan::rarecurve()把每个样方的物种数稀释到相同采样强度下再比较,同时画稀疏曲线判断曲线是否到达平台期。

library(iNEXT) # iNEXT默认第一个维度是物种,所以转置一下 inext_out <- iNEXT(t(species_matrix), datatype = "abundance", q = 0, se = TRUE) ggiNEXT(inext_out, type = 1)

画出来的曲线如果到样本尾部仍然快速上升,说明调查还没采够,这时候报告的物种数是不靠谱的。如果曲线已经明显趋平,说明继续增加采样对物种数的影响不大,可以放心比较。

2.3 组间差异:为什么生态学常用PERMANOVA而不是MANOVA

比较不同生境之间的群落是否显著不同,新手最容易犯的错是直接对每个物种做t检验,或者把物种矩阵塞进MANOVA。问题在于物种多度数据本质上是成分数据,大量零值、分布偏态、物种之间高度相关,这些条件把MANOVA的假设破坏得很彻底。

生态学的标准做法是先计算样方之间的相异度矩阵,最常用的是Bray-Curtis相异度,然后在这个距离框架上做置换检验。PERMANOVA就是干这个的:

bc_dist <- vegdist(species_matrix, method = "bray") # PERMANOVA:检验不同生境间群落组成是否有显著差异 adonis2(bc_dist ~ habitat, data = env_data, permutations = 999) # ANOSIM:基于秩的相似性检验,关注组间距离是否大于组内距离 anosim(bc_dist, env_data$habitat, permutations = 999) # 多元离散度检验:判断P值到底来自位置差异还是离散度差异 permdisp <- betadisper(bc_dist, group = env_data$habitat) permutest(permdisp)

adonis2()的结果里,R²是生境分组能解释的群落方差比例,相当于效应量,p值来自置换检验。解释的时候要同时看这两个数,R²很小但p值显著,说明效应真实但微弱。betadisper()这一步很多人会跳过,但如果不检验多元离散度,PERMANOVA的显著结果可能仅仅因为某一组的组内距离更大,而不是群落组成真的不同。

3. 排序分析实战:先看全局结构,再做约束解释

排序是生态数据科学里最有"分析感"的部分,它的目标是把样方在物种空间里的关系投射到低维平面上,让我们能用眼睛看出群落的梯度结构。

3.1 排序方法选型:PCA、CA、NMDS、RDA、CCA怎么选

不同排序方法基于不同的数学模型和距离定义,选错了分析的前提就不成立。先看方法选型表:

方法响应模型距离/数学基础适用场景主要函数
PCA线性欧氏距离环境变量数据,或经转化后的物种数据rda()
CA单峰卡方距离原始多度矩阵,梯度较长cca()
DCA单峰去趋势的卡方距离长梯度数据消除弓形效应decorana()
NMDS任意任意距离(常用Bray-Curtis)各类群落数据,稳健灵活metaMDS()
RDA线性欧氏距离环境变量约束下的线性响应rda()
CCA单峰卡方距离环境变量约束下的单峰响应cca()
db-RDA线性任意相异度用灵活距离做约束排序dbrda()(vegan较新版本)

一个实用的判断标准是看DCA第一轴的长度:用decorana(species_matrix)跑一下,如果第一轴梯度长度超过4个标准差单位,说明物种沿梯度呈明显的单峰响应,该用CA或CCA;如果小于3,线性方法PCA或RDA更合适;介于3到4之间,两种都说得通,可以根据解释需要选择。我自己常用的组合是:先跑NMDS做无约束探索,再用RDA做环境约束,这两个方法覆盖了大多数植被调查场景。

3.2 PCA与NMDS:无约束排序先看清数据结构

PCA对原始多度数据直接运算会有一个老问题——双零问题。两个样方都缺一个物种,在欧氏距离里会被算成"相似",但这个缺失可能只是采样碰巧没碰到,不代表群落真的相似。解决办法是先对物种矩阵做Hellinger转化,把多度转成相对多度的平方根。转化之后,借用数学上说就是用弦距离代替欧氏距离,双零问题的影响被大幅削弱。

hel_sp <- decostand(species_matrix, method = "hellinger") pca_res <- rda(hel_sp) summary(pca_res) # 看特征值和解释方差比例 # 把环境变量投影到排序空间 ef <- envfit(pca_res, env_data[, c("elevation", "soil_pH", "organic_matter", "cover")], permutations = 999) plot(pca_res, type = "n") points(pca_res, display = "sites", pch = 19, col = as.numeric(factor(env_data$habitat))) plot(ef, p.max = 0.05)

NMDS是另一种思路,它不假设物种响应的具体形式,只用样方间距离的秩次关系来找低维坐标。对生态数据这种高噪声、非线性的结构,NMDS通常比PCA稳得多。

set.seed(123) nmds_res <- metaMDS(species_matrix, distance = "bray", k = 2, trymax = 100) nmds_res$stress # 考察拟合质量 ordiplot(nmds_res, type = "n") points(nmds_res, display = "sites", pch = 19, col = as.numeric(factor(env_data$habitat))) ordiellipse(nmds_res, env_data$habitat, kind = "ehull", lty = 2)

NMDS唯一的代价是它没有解析解,需要用迭代算法反复寻找最优点位,所以一定要看stress值:小于0.05表示配置非常好,小于0.1良好,小于0.2可以接受,超过0.2就说明二维配置不可信,需要增加维度或者检查数据。我在一次实际分析里把k设为2后stress跑到0.23,改成k=3才降到0.11,这时就必须接受三维配置,输出时要明确说明。

3.3 RDA与CCA:让环境变量解释群落差异

无约束排序把结构找出来之后,下一个问题自然是:哪些环境因子在驱动这个结构?这就是约束排序的工作。

跑RDA之前先检查环境变量之间的共线性。解释变量高度相关时,回归系数会非常不稳定。经验法则是方差膨胀因子VIF超过10的变量就要考虑剔除。

library(car) vif(lm(as.matrix(hel_sp) ~ elevation + soil_pH + organic_matter + cover, data = env_data))

RDA本身对环境变量做了中心化,但我还是建议在公式里对量纲差异大的变量做scale()处理,比如海拔从300到1800,土壤pH只在4.5到6.5之间,不标准化会让系数解释很别扭。

rda_res <- rda(hel_sp ~ scale(elevation) + scale(soil_pH) + scale(organic_matter), data = env_data) # 整体显著性检验 anova(rda_res, permutations = 999) # 各解释变量的边际贡献 anova(rda_res, by = "terms", permutations = 999) # 校正后的解释率 RsquareAdj(rda_res)$adj.r.squared

约束排序的图不是拿来看着好看就算完的,读图有顺序:先看整体解释率,再看各轴解释的比例,最后看物种在环境箭头方向的投影。箭头越长,表示该环境变量对排序结构的贡献越大;箭头指向象限的物种,倾向于在该环境变量高值处出现。

如果DCA梯度长度大于4,说明物种响应是单峰的,这时约束排序应该用CCA。CCA对原始多度矩阵直接运算,处理长梯度的效果比RDA好:

cca_res <- cca(species_matrix ~ elevation + soil_pH, data = env_data) anova(cca_res, by = "terms", permutations = 999)

还有一个进阶操作:偏约束排序。用Condition()把协变量(比如空间变量)先抵消掉,再看剩余的环境效应。这在评估"排除了某个因素之后,另一个因素还解释多少"时特别有用。

3.4 变差分解:量化各组环境因子的独立贡献

RDA能告诉我们所有环境变量合计解释了百分之多少,但如果想知道"海拔独立解释多少、土壤因子独立解释多少、两者重叠的部分有多少",就要做变差分解。

vp <- varpart(hel_sp, ~ scale(elevation), ~ scale(soil_pH) + scale(organic_matter), data = env_data) plot(vp, digits = 2)

变差分解图里,每个椭圆代表一组变量能解释的方差比例,重叠区域是共享解释部分,不重叠的区域是某组变量的纯贡献。解释时要注意:共享部分通常不能归因于任何单一变量,因为环境因子在自然界本来就是协同变化的。别为了追求"纯贡献"好看而强行把变量拆得很碎,变量越分越细,共享部分就越大,结论反而越难说清。

4. 空间建模实战:从点样方到连续地图

统计分析告诉我们群落有差异、环境在驱动,下一步读者和审稿人都会问:这个格局在空间上长什么样?这就进入空间建模。

4.1 先别急着插值:空间自相关检验

生态数据天然不是独立样本,相邻样方的物种组成往往比相距很远的样方更相似。这种空间自相关如果存在,所有常规统计检验的有效样本量都比名义上的少,容易产生假阳性。所以在做任何空间推断之前,先检验一下数据里到底有没有自相关,这既是统计规范,也是防止被审稿人一票否决的保险。

这里要用投影坐标,不能直接用经纬度算距离。我的样方坐标是UTM投影,单位是米,可以直接用于近邻分析。

library(sf) library(spdep) divesp <- st_as_sf(div_df, coords = c("x", "y"), crs = 32650) # 基于k近邻构建空间权重矩阵 coords <- st_coordinates(divesp) nb <- knn2nb(knearneigh(coords, k = 4)) listw <- nb2listw(nb) # Moran's I检验 moran.test(divesp$shannon, listw)

Moran's I的取值范围大致在-1到1之间,0表示无空间自相关,正值表示相似值在空间上聚集,负值表示高低值交替分布。moran.test()的p值显著,就说明样方差异不是随机的,有空间结构可以建模。

4.2 变异函数与克里金插值:把多样性铺成连续面

确认存在空间自相关之后,用地质统计学的框架做插值。克里金插值比普通反距离加权好的地方在于,它不仅给出预测值,还给出预测方差,也就是每个位置的估计误差。

第一步是看变异函数,它描述的是样方间距与样方值差异之间的关系。拟合变异函数模型时需要理解三个参数:块金值nugget是距离趋近于零时的方差,反映测量误差和微小尺度的变异;基台值sill是方差达到平台的值,对应总方差;变程range是空间自相关存在的最大距离,超过这个距离,两个位置的观测值就不相关了。

library(gstat) v_emp <- variogram(shannon ~ elevation, data = divesp) v_fit <- fit.variogram(v_emp, model = vgm(psill = 0.12, model = "Sph", range = 400, nugget = 0.02)) plot(v_emp, v_fit)

公式里的~ elevation表示这是泛克里金,也就是说Shannon指数存在随海拔变化的空间趋势,克里金在去除趋势之后对残差做空间插值,再叠加回趋势面。这种带外部漂移的做法,比单纯对观测值做普通克里金更适合环境因子驱动的生态变量。

预测和出图:

library(terra) # 环境栅格 r_elev 是从数字高程模型裁剪的研究区图层 grd_sp <- as(r_elev, "SpatialGridDataFrame") kg_res <- krige(shannon ~ elevation, locations = divesp, newdata = grd_sp) pred_r <- rast(kg_res["var1.pred"]) plot(pred_r, col = hcl.colors(50, "Blue-Red"))

对插值结果必做的验证是留一交叉验证,krige.cv()会给出每个真实观测点在剔除自身之后的预测误差。均方根误差和预测误差的均值能直接反映插值的可靠性。我见过不少把插值面画得五彩斑斓、但交叉验证误差值大得离谱的报告,这种图数据上是站不住的。

4.3 物种分布模型:从环境栅格到分布概率

如果说克里金是对单个群落指标做空间外推,物种分布模型则是在拟合"某个物种出现与否受哪些环境变量控制"的关系,并把这个关系外推到整个研究区域。它的用途很广,评估保护区适宜生境、预测入侵物种扩散、模拟气候变化影响都靠它。

生态学里有两套主流做法。有完整的存在/不存在调查数据时,用逻辑回归、GAM或者随机森林都是常规选择;只有存在记录时,常用Maxent的思路,在R里用maxnet包实现。

library(maxnet) # 存在点 p_vec 和环境变量矩阵 env_X(可从栅格中提取) # 再加一组从背景区域随机采样的伪不存在点 set.seed(123) mnet <- maxnet(p = c(rep(1, sum(p_vec)), rep(0, 1000)), data = rbind(env_pres, env_bg), f = maxnet.formula(c(rep(1, sum(p_vec)), rep(0, 1000)), rbind(env_pres, env_bg), classes = "lq")) # 对整个研究区栅格预测 pred_map <- predict(r_stack, mnet, type = "cloglog") plot(pred_map)

这里有一个必须在方法部分写清楚的点:伪不存在点的采样策略没有唯一正确答案。从整个背景区域随机采,默认的前提是物种在研究区各处都有被调查到的机会;如果实际调查只在道路附近做过,背景采样也要模拟这个偏差,否则模型会把道路可及性当成环境变量的效应。这个坑我踩过一次,后来学乖了,采样偏差永远写进局限性那段。

模型评估也不能只看预测图顺不顺眼。对存在/不存在数据用AUC、TSS这类指标做交叉验证,对只有存在记录的数据,至少要做空间分块的交叉验证,把同一片区域的存在点同时放进训练集会导致乐观偏估计。环境变量之间的相关性同样要提前排查,两个高度相关的气候变量同时进模型,系数解释会变得非常不稳定。

5. 一次完整实战:模拟项目X的植被调查全流程复现

前面三个部分分别讲了统计、排序、空间三条支线,现在把它们串成一条完整的流水线。这个演示项目的名字和数据结构前面已经交代过,下面按我自己跑项目的顺序逐步展开。

5.1 文件夹结构和脚本划分

项目开始前先建好文件夹结构,这个习惯能省掉后面大量找文件的痛苦:

sim_project_X/ ├── data/ │ ├── raw/ # 原始调查表,只读 │ └── processed/ # 清洗后的中间数据 ├── scripts/ │ ├── 01_import.R │ ├── 02_diversity.R │ ├── 03_ordination.R │ └── 04_spatial.R ├── figures/ └── outputs/

脚本划分的原则是一个脚本只负责一个阶段,01负责读原始数据并输出处理后的数据到processed/,02读进来算多样性并输出结果表和箱线图,以此类推。这样任何一个步骤出错,都不用重跑整个分析。每个脚本开头固定写入set.seed(123),因为排序和置换检验都有随机性,不设种子的话结果每次跑都不一样。

5.2 从原始表到四张核心产出图

第一步产出多样性结果表div_df,然后画箱线图比较不同生境之间的丰富度和Shannon指数:

ggplot(div_df, aes(x = habitat, y = shannon, fill = habitat)) + geom_boxplot() + geom_jitter(width = 0.2, size = 1.5) + labs(x = "生境类型", y = "Shannon多样性指数") + theme_minimal(base_size = 14)

第二步跑NMDS,输出排序图加95%置信椭圆,这张图直接告诉读者三个生境的群落组成在排序空间里是否分得开。第三步跑RDA,输出带环境箭头的三序图,说明海拔和土壤pH对群落结构的主驱动地位。第四步把Shannon指数做克里金插值,输出整个保护区的多样性预测面,这张图回答的是"多样性热点在哪些位置"。

四张图的证据链是连贯的:统计检验证明差异存在,NMDS展示差异的结构,RDA解释差异的来源,空间图展示差异的分布。写结果部分时按这个逻辑叙述,层层递进,比单独堆四张图有说服力得多。

5.3 每一个数字都对应一个结论

在报告里写结果的时候,要养成的习惯是把输出结果里的每个关键数值都对应到一句人话上。NMDS的stress是0.08,对应的是"二维配置可靠";RDA的整体置换检验p小于0.001,校正解释率0.23,对应的是"四个环境变量解释了约两成多的群落变异";变差分解里海拔的独立贡献是0.09,对应的是"海拔作为单一梯度有不可替代的解释力"。

我自己写结果时最忌讳的一句话是"排序图表明……",因为图本身不能表明任何事,图和统计量结合才构成证据。先写统计量,再用图作为视觉佐证,文章的逻辑才立得住。

6. 我踩过的坑:生态数据项目里那些隐形时间杀手

最后这部分都是真金白银买来的教训。做生态数据项目,真正费时间的往往不是模型本身,而是那些看起来很不起眼、一折腾就是半天的细节。

6.1 数据表的问题占了项目一半的时间

读进来的数据第一件事永远是str()和summary(),不要直接跑模型。我遇到过样方ID因为Excel自动把"1201"识别成了数字,变成"1201"和"1201.0"两套写法,导致join之后一半样方变NA的情况。中文物种名里的全角括号、行末不可见空格,都会让图例显示异常或者排序报错。多度和盖度混着填,数值数量级差好几个级别,Bray-Curtis相异度会被高盖度的物种主导。

还有一个常见错误:把NA当成0传给vegdist()。在生态数据里,NA通常意味着"没调查",0意味着"调查了但没出现",这两者绝对不能混。读数据时先用is.na()排查一遍,该补的调查记录就回去补,补不上的记录宁可删样方也不要硬塞。

6.2 统计方法的误用与过度解读

最常见的问题有三个。第一,拿原始物种数直接比来比去,不检查样方间采样强度差异,正确的操作是先做稀疏化。第二,PERMANOVA的p值显著就急着写结论,不跑betadisper(),结果显著性可能来自组内离散度差异而不是位置差异。第三,把adonis2()的R²当成"方差解释率"直接引用,这个R²确实是效应量,但它是样本内的效应量,置换检验的p值才是推断依据,两者要分开表述。

排序图过度解读也很常见。NMDS图上两个样方离得近,只能说明相异度小,不要脑补出"生态位重叠"之类的高级结论;RDA箭头之间的角度在低解释率下不稳定,别用它硬推环境变量之间的相关性。

6.3 空间坐标与包生态的迁移成本

空间分析的坑集中在坐标上。经纬度算Moran's I的邻接矩阵不会直接报错,但邻接关系会被纬度的收敛效应扭曲;算变异函数的距离矩阵更是必须用投影坐标,单位是度的"range"完全没法解释。我调过一整晚的克里金参数,最后发现问题是样方坐标还在4326坐标系里,把2 km的距离写成了0.02度。

包生态这几年变化也大,rgdal、raster逐渐被sf、terra取代,vegan里adonis被adonis2取代。代码报错时先考虑是不是包版本问题,sessionInfo()记录下的版本号是复现分析的第一道保险。我现在的习惯是每次提交结果都附带一份sessionInfo()保存的文本文件。

6.4 几个让我少加班的日常习惯

所有涉及随机置换的分析都先set.seed(),不是为了研究严谨性,是为了你自己能复现昨天的输出。中间结果用saveRDS()存下来,前面步骤改个参数,后面就不用全部重跑。脚本里的输出文件名带上日期和版本,比如fig3_rda_v2.png,比"最终版最终版2"这种命名方式靠谱得多。

最后分享一个我自己的习惯:跑完一套流程之后,先不看那些漂亮的排序图和插值图,把每个模型的关键诊断量打印出来——stress、VIF、交叉验证误差、Moran's I——全部过一遍眼睛,确认没有红色警报,再去完善图表的美观。数据科学项目的成就感来自每一步都站得住脚,而不是图有多斑斓。这套"统计—排序—空间"的R语言工作流,我每次接到新项目都会完整跑一遍,希望你下次拿到自己的样方数据时,也能直接在这套框架上开工。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询