R语言方差分析:单因素与双因素ANOVA完整流程

R语言方差分析:单因素与双因素ANOVA完整流程

"施肥处理组和对照组之间,植物多样性有差异吗?""不同生态系统对氮添加的响应一样吗?"多组间比较是生态研究里的高频问题。有人习惯做两两 t 检验,组一多就繁琐,还要担心多重比较把犯错的概率放大。这时候该请出 方差分析(ANOVA)

一次 F 检验回答"所有组均值是否相等",显著后再靠事后比较定位差异组。我们拿"氮添加水平 × 生态系统类型对植物多样性的影响"当例子,从前提检验、单因素 ANOVA,走到双因素 ANOVA 和交互效应,把一篇论文里会用到的主流程完整走一遍。

一、方差分析在做什么

单因素 ANOVA 检验一个分类因子(如氮添加水平)对连续响应变量(如 Shannon 多样性指数)的影响,核心是把总变异拆成两部分:

  • 组间变异:各组均值与总均值之差,反映处理效应
  • 组内变异:各组内部个体间的差异,反映随机误差

F 统计量 = 组间均方 / 组内均方。处理效应远大于随机误差时 F 变大,p < 0.05 就拒绝"各组均值相等",说明至少有一组跟其他组不一样。

双因素 ANOVA 同时考察两个因子(如氮添加水平 × 生态系统),还能检验二者的交互作用,也就是"一个因子的效应是否依赖另一个因子的水平"。比如氮添加对草地的抑制可能比对农田更强,这种效应差异就是交互。

ANOVA 有三大前提假设,不满足时结果不可靠:

前提 检验方法 要求
独立性 实验设计保证 各样本相互独立
正态性 Shapiro-Wilk 检验(对残差 p > 0.05(不拒绝正态)
方差齐性 Levene 检验 p > 0.05(不拒绝齐性)

注意:前提检验的零假设是"假设成立"(残差正态 / 方差齐性),所以 p > 0.05 表示没有证据拒绝假设、前提可认为满足。若 p < 0.05,单因素方差不齐时优先用 Welch ANOVAoneway.test());双因素不能简单套 Kruskal-Wallis(它检验的是秩分布,不是"方差不齐的 ANOVA 替代品"),要按研究设计选稳健 ANOVA、置换检验或广义/混合效应模型。

二、数据准备

我们用的是一份仿真生态学数据,模拟"氮添加水平 × 生态系统"双因素实验对植物 Shannon 多样性的影响。实验设计是 3 × 3 = 9 组,每组 20 个样方,共 180 行。

因子 水平
Nlevel(氮添加水平) Control(对照) / Low_N(低氮) / High_N(高氮)
Ecosystem(生态系统) Grassland(草地) / Forest(森林) / Cropland(农田)

响应变量是 Shannon 多样性指数(连续变量),另有 Simpson、Pielou、物种数 S 备用。

r 复制代码
# 读取数据
dat <- read.csv("anova_diversity.csv", header = TRUE)
dat$Ecosystem <- factor(dat$Ecosystem)
dat$Nlevel <- factor(dat$Nlevel, levels = c("Control", "Low_N", "High_N"))
head(dat)

仿真数据规律是这么设定的:氮添加降低植物多样性,且草地被设定为对氮添加最敏感,这是后面交互效应的重要伏笔。依据是生态学的经典结论:外源氮输入提高土壤氮有效性后,会筛选出氮利用效率高的优势种促其竞争性生长,适应低养分生境的物种被竞争排斥而消失,群落多样性随之下降(Bobbink et al., 2010; Stevens et al., 2004)。草地群落物种组成对养分变化敏感、周转快,通常认为比森林和农田的响应更剧烈。

三、核心操作:从前提检验到双因素 ANOVA

第一步:前提检验

正态性检验:对残差,不是原始数据!

很多初学者对原始数据 做 Shapiro-Wilk 检验,这是常见误区。ANOVA 的正态性假设针对的是模型残差,即使各组观测值本身偏离正态,只要残差近似正态,参数检验的结果依然有效:

r 复制代码
library(car)
# 先拟合单因素模型,再检验残差
fit1 <- aov(Shannon ~ Nlevel, data = dat)
shapiro.test(residuals(fit1))

输出示例:

ini 复制代码
	Shapiro-Wilk normality test
W = 0.99373, p-value = 0.6408

p = 0.641 > 0.05,没有足够证据拒绝残差正态的假设,可认为残差近似正态。同时可以结合 Q-Q 图综合判断,下图就是残差的 Q-Q 图,散点大致沿对角线分布、两端只有轻微偏离,直观支持上面的结论。

r 复制代码
library(ggplot2)
p_qq <- ggplot(data.frame(res = residuals(fit1)), aes(sample = res)) +
  stat_qq(color = "#5B8CC9", alpha = 0.7) +
  stat_qq_line(color = "#1F3B73", linewidth = 0.5) +
  labs(title = "Q-Q plot of ANOVA residuals", x = "Theoretical quantiles",
       y = "Sample quantiles") +
  theme_classic(base_size = 8, base_family = "Arial") +
  theme(axis.line = element_line(linewidth = 0.4),
        axis.ticks = element_line(linewidth = 0.4),
        panel.grid = element_blank())
方差齐性:Levene 检验

方差齐性要求各组方差相近,推荐用 Levene 检验car::leveneTest() 默认以中位数为中心,属于 Brown-Forsythe 型,对非正态数据通常比经典的 Bartlett 检验更稳健:

r 复制代码
leveneTest(Shannon ~ Nlevel, data = dat)

输出示例:

r 复制代码
      Df F value  Pr(>F)
group  2  1.9516 0.1451
     177

p = 0.145 > 0.05,没有足够证据拒绝方差齐性的假设,可认为各组方差相近。

第二步:单因素 ANOVA + Tukey 多重比较

前提检验通过后,用 aov() 拟合单因素模型,summary() 查看 F 检验结果:

r 复制代码
summary(fit1)

输出示例:

r 复制代码
             Df Sum Sq Mean Sq F value    Pr(>F)
Nlevel        2 26.860  13.430   125.2 < 2.2e-16 ***
Residuals   177 18.997   0.107

F(2, 177) = 125.2, p < 0.001,氮添加水平显著影响 Shannon 多样性。但整体显著不代表每组都不同,需要事后多重比较定位具体差异。

为什么不用两两 t 检验?三组两两比较要算 3 个 t 检验,每个按 α=0.05 判断时,整体犯第一类错误的概率会膨胀到约 1-(1-0.05)³ ≈ 14%。Tukey HSD(Honestly Significant Difference) 用学生化极差分布校正了这种膨胀,把整体错误率(family-wise error rate)控制在 α 以内;只有当目标是"每个处理都与对照比"而非两两互比时,才改用 Dunnett 检验

r 复制代码
TukeyHSD(fit1)

输出示例:

bash 复制代码
$Nlevel
                     diff       lwr       upr    p adj
Low_N-Control  -0.5491233 -0.690464 -0.407783 0.000000
High_N-Control -0.9419133 -1.083254 -0.800573 0.000000
High_N-Low_N   -0.3927900 -0.534130 -0.251450 0.000000

三组两两比较均显著(p adj 均 < 0.001)。diff 是均值差,lwr/upr 是 95% 置信区间。High_N 比 Control 平均低 0.94,Low_N 比 Control 低 0.55,氮添加显著抑制了多样性。

把结果画成箱线图:三组箱体用蓝系渐变(浅蓝 → 深蓝)呈现氮添加水平递增,箱体上方标注 Tukey 字母 a/b/c,字母不同表示组间差异显著。从左到右箱线图中位数逐级下降(约 2.86 → 2.23 → 1.89),跟统计结果一致(均值分别为 2.81、2.26、1.87)。

r 复制代码
library(multcompView)   # Tukey 字母计算
# 计算 Tukey 字母(a/b/c)
tk <- TukeyHSD(fit1)$Nlevel
lv <- levels(dat$Nlevel)
pmat <- matrix(1, 3, 3, dimnames = list(lv, lv))
for (pr in rownames(tk)) {
  # 注意:按 "-" 拆分组名对假设组名不含连字符成立;
  # 若你的因子水平名含 "-"(如 "Low-N"),改用 multcompLetters4() 等更稳健的接口
  v <- strsplit(pr, "-")[[1]]
  pmat[v[2], v[1]] <- pmat[v[1], v[2]] <- tk[pr, "p adj"]
}
let1 <- multcompLetters(pmat)$Letters
lab1 <- data.frame(Nlevel = names(let1), letters = let1,
                   y = tapply(dat$Shannon, dat$Nlevel, max) + 0.1)

p_oneway <- ggplot(dat, aes(x = Nlevel, y = Shannon, fill = Nlevel)) +
  geom_boxplot(alpha = 0.85, outlier.shape = NA, linewidth = 0.4) +
  geom_jitter(width = 0.12, size = 0.6, alpha = 0.35, color = "grey35") +
  geom_text(data = lab1, aes(x = Nlevel, y = y, label = letters),
            size = 3, fontface = "bold") +
  scale_fill_manual(values = c(Control = "#D6E4F0", Low_N = "#5B8CC9",
                               High_N = "#1F3B73")) +
  labs(x = "Nitrogen addition level", y = "Shannon diversity index") +
  theme_classic(base_size = 8, base_family = "Arial") +
  theme(legend.position = "none",
        axis.line = element_line(linewidth = 0.4),
        axis.ticks = element_line(linewidth = 0.4),
        panel.grid = element_blank())

几个关键参数:

参数 作用 本图设置
geom_boxplot(alpha = 0.85) 箱线图,半透明 透明度 0.85
outlier.shape = NA 隐藏异常值散点 避免与 jitter 混淆
geom_jitter(width = 0.12) 叠加原始数据点 灰色小点展示分布
geom_text(data = lab1, ...) 箱体上方标注 Tukey 字母 加粗 a/b/c
scale_fill_manual() 自定义箱体配色 蓝系渐变(浅→深)
theme_classic(base_size = 8) 极简科研主题 Arial、紧凑字号、无网格

第三步:双因素 ANOVA ------ 氮添加 × 生态系统

同时考虑两个因子。先拟合主效应模型(加法模型,不含交互):

r 复制代码
# 主效应模型:只考虑两个因子的各自影响
fit2a <- aov(Shannon ~ Nlevel + Ecosystem, data = dat)
summary(fit2a)

输出示例:

r 复制代码
             Df F value    Pr(>F)
Nlevel        2 154.465 < 2.2e-16 ***
Ecosystem     2  21.693 3.83e-09 ***
Residuals   175

两个因子都极显著。接着拟合含交互模型,看氮添加效应是否随生态系统变化:

r 复制代码
# 含交互模型:考虑氮添加与生态系统的交互作用
fit2b <- aov(Shannon ~ Nlevel * Ecosystem, data = dat)
summary(fit2b)

输出示例:

r 复制代码
                  Df  F value    Pr(>F)
Nlevel             2 164.3332 < 2.2e-16 ***
Ecosystem          2  23.0791 1.34e-09 ***
Nlevel:Ecosystem   4   3.7951  0.005537 **
Residuals        171

交互项 Nlevel:Ecosystem 显著(F = 3.80, p = 0.0055),说明氮添加对多样性的效应依赖生态系统类型,不同生态系统的响应强度不同。

前提检验要对最终使用的模型做。 前面验的是单因素模型 fit1 的残差,但真正要解读的是双因素交互模型 fit2b。正确做法是对最终模型 fit2b 的残差再检验一次前提(正态性 + 方差齐性),而不是默认单因素前提成立就推广到双因素:

r 复制代码
# 对最终双因素模型做前提检验
shapiro.test(residuals(fit2b))
leveneTest(residuals(fit2b) ~ interaction(dat$Nlevel, dat$Ecosystem))

输出示例:

ini 复制代码
	Shapiro-Wilk normality test
W = 0.99601, p-value = 0.919

Levene's Test for Homogeneity of Variance (center = median)
       Df F value Pr(>F)
group   8  0.6552 0.7302
      171

fit2b 残差正态 p = 0.919、方差齐性 p = 0.730,双因素模型的前提也得到满足。无论跑单因素还是双因素,都要对最终使用的模型做残差诊断,这是科研分析的习惯,而不是"检验一次就一劳永逸"。

公式写法速记: A + B 是主效应模型;A * B 自动展开为 A + B + A:B(含交互)。交互显著时应保留交互项解读;交互不显著时不能仅凭 p 值机械删掉交互项,要结合研究设计、预先设定的假设和模型简化原则判断。

分面箱线图把交互画出来:三个面板(Cropland / Forest / Grassland)中,氮添加均使多样性下降,每面板内标注 Tukey 字母。Grassland 面板对照组最高(约 3.2)且下降最陡,直观展示交互的来源。

r 复制代码
# 每个 Ecosystem 面板内的 Tukey 字母
let2 <- by(dat, dat$Ecosystem, function(d) {
  m <- aov(Shannon ~ Nlevel, data = d)
  tk <- TukeyHSD(m)$Nlevel
  lv <- levels(d$Nlevel)
  pmat <- matrix(1, 3, 3, dimnames = list(lv, lv))
  for (pr in rownames(tk)) {
    v <- strsplit(pr, "-")[[1]]
    pmat[v[2], v[1]] <- pmat[v[1], v[2]] <- tk[pr, "p adj"]
  }
  multcompLetters(pmat)$Letters
})
lab2 <- do.call(rbind, lapply(names(let2), function(ec) {
  data.frame(Ecosystem = ec, Nlevel = names(let2[[ec]]), letters = let2[[ec]],
             y = tapply(dat$Shannon[dat$Ecosystem == ec],
                        dat$Nlevel[dat$Ecosystem == ec], max) + 0.12)
}))

p_twoway <- ggplot(dat, aes(x = Nlevel, y = Shannon, fill = Nlevel)) +
  geom_boxplot(alpha = 0.85, outlier.shape = NA, linewidth = 0.4) +
  geom_jitter(width = 0.12, size = 0.6, alpha = 0.35, color = "grey35") +
  geom_text(data = lab2, aes(x = Nlevel, y = y, label = letters),
            size = 3, fontface = "bold") +
  facet_wrap(~ Ecosystem, ncol = 3) +
  scale_fill_manual(values = c(Control = "#D6E4F0", Low_N = "#5B8CC9",
                               High_N = "#1F3B73")) +
  labs(x = "Nitrogen addition level", y = "Shannon diversity index") +
  theme_classic(base_size = 8, base_family = "Arial") +
  theme(legend.position = "none",
        strip.text = element_text(face = "bold"),
        axis.line = element_line(linewidth = 0.4),
        axis.ticks = element_line(linewidth = 0.4),
        panel.grid = element_blank())

第四步:交互效应可视化

交互作用用文字描述不够直观,交互效应图(interaction plot)是很好的辅助呈现方式,把各因子组合的均值连成线。注意:线条不平行通常提示可能存在交互,但交互是否存在应以 ANOVA 模型中的交互项 F 检验为准,交互图是直观展示而非正式检验:

r 复制代码
# 计算分组均值
mu <- aggregate(Shannon ~ Nlevel + Ecosystem, data = dat, FUN = mean)

# 交互效应图:连线是否平行反映交互是否显著
p_interact <- ggplot(mu, aes(x = Nlevel, y = Shannon,
                             group = Ecosystem, color = Ecosystem)) +
  geom_point(size = 2.2) +
  geom_line(linewidth = 0.7) +
  scale_color_manual(values = c(Grassland = "#2C7A57", Forest = "#7A4D8E",
                                Cropland = "#B85C4A")) +
  labs(x = "Nitrogen addition level", y = "Mean Shannon diversity index") +
  theme_classic(base_size = 8, base_family = "Arial") +
  theme(axis.line = element_line(linewidth = 0.4),
        axis.ticks = element_line(linewidth = 0.4),
        panel.grid = element_blank())

三条线都下降但不平行:Grassland 线(绿色)最陡,从约 3.10 降至约 1.87;Forest(紫色)与 Cropland(红棕)两线近乎平行、下降较缓(分别约 2.80→2.01 与 2.53→1.72)。草地降幅最大,正是本仿真数据"草地最敏感"的设定,所以交互项显著。

四、进阶:交互显著后用 emmeans 做简单效应

前面分面箱线图那种按 Ecosystem 分别做 ANOVA + Tukey 的做法,教学上直观,但严格说把数据拆成 3 个子集分别分析,每个子集用各自的残差自由度,并未完全利用完整模型的信息,且跨三个生态系统的整体推断存在多重比较问题。更规范的做法是用 emmeans 包在完整模型上直接计算 simple effects(简单效应,即"在某个 Ecosystem 内比较 Nlevel 各组"):

r 复制代码
library(emmeans)
fit2b <- aov(Shannon ~ Nlevel * Ecosystem, data = dat)
emm <- emmeans(fit2b, pairwise ~ Nlevel | Ecosystem, adjust = "tukey")
emm$contrasts

输出示例(部分):

ini 复制代码
Ecosystem = Grassland:
 contrast          estimate    SE  df t.ratio p.value
 Control - Low_N      0.732 0.0904 171   8.097 <0.0001
 Control - High_N     1.225 0.0904 171  13.548 <0.0001
 Low_N - High_N       0.493 0.0904 171   5.450 <0.0001

emmeans 在完整模型上使用统一的残差方差和自由度(df = 171) ,三个生态系统用同一套误差估计,比拆分子集更准确、更严谨。本例中其结论与分面箱线图的字母标注一致(每个生态系统内三组两两显著),但两者方法不同:拆子集是"三套独立分析",emmeans 是"一个模型内的简单效应比较"。科研论文中推荐用 emmeans 类方法,按面板分析更适合教学演示。

五、结果解读

从分析和图中可以得出三层结论:

1. 氮添加普遍抑制多样性。 单因素 ANOVA(F = 125.2, p < 0.001)显示氮添加水平显著影响 Shannon 多样性。Tukey 多重比较显示三组两两显著,箱线图上标注的字母 a/b/c 各不相同,High_N 组均值比 Control 低 0.94。该趋势方向与氮沉降抑制植物多样性的经典生态学结论一致(本数据为仿真生成,仅呈现方法流程,不构成对真实生态规律的验证)。

2. 生态系统间存在本质差异。 无论氮添加水平如何,Grassland 的多样性整体最高,Forest 居中,Cropland 最低,Ecosystem 主效应极显著(F = 21.7, p < 0.001)。该梯度方向与农业集约化管理降低植物多样性的普遍认识相符,长期耕作、除草和施肥会抑制农田中非目标物种的定殖,使群落物种组成趋于单一。这里的生态系统间差异同样来自仿真数据的设定,仅用于演示双因素模型对第二因子的检验,不对真实农田与自然生态系统多样性作因果断言。

3. 交互作用揭示响应差异。 交互项显著(F = 3.80, p = 0.0055)表明氮添加的影响不是均匀的:从交互效应图可以看到,Grassland 线下降最陡,Forest 与 Cropland 两线近乎平行、下降较缓,交互主要来自草地的更陡响应。这一模式源于仿真数据的设定(草地最敏感),并非从真实观测中归纳得出。

Tukey 字母速读法: 箱线图顶部的字母来自事后检验的分组标记,同一字母的组差异不显著,不同字母的组差异显著。三组均标为 a、b、c(互不相同),意味着任意两组间差异都达到了显著水平。

六、完整可运行代码

下面是一份自包含的 R 脚本,复制到 RStudio 即可运行(数据文件随文章提供,可直接下载使用)。它比正文代码多了统一的 theme_pub() 极简主题和 tukey_letters() 字母计算函数,方便直接复用:

r 复制代码
# ============================================================
# 单因素 & 双因素 ANOVA 完整流程
# ============================================================
# 首次运行请先安装包:
# install.packages(c("car", "ggplot2", "multcompView"))

library(car)
library(ggplot2)
library(multcompView)   # Tukey 字母

# --- 读取数据 ---
dat <- read.csv("anova_diversity.csv", header = TRUE)
dat$Ecosystem <- factor(dat$Ecosystem)
dat$Nlevel <- factor(dat$Nlevel, levels = c("Control", "Low_N", "High_N"))
head(dat)

# --- 统一主题(Nature 风格极简主题) ---
theme_pub <- function(base_size = 8, base_family = "Arial") {
  theme_classic(base_size = base_size, base_family = base_family) +
    theme(
      axis.line = element_line(linewidth = 0.4, colour = "black"),
      axis.ticks = element_line(linewidth = 0.4, colour = "black"),
      axis.title = element_text(size = base_size + 1),
      axis.text = element_text(size = base_size - 0.5),
      legend.title = element_text(size = base_size),
      legend.text = element_text(size = base_size - 1),
      strip.text = element_text(size = base_size, face = "bold"),
      legend.key.size = unit(3, "mm"),
      panel.grid = element_blank()
    )
}
# 色板
cols_nlevel <- c(Control = "#D6E4F0", Low_N = "#5B8CC9", High_N = "#1F3B73")
cols_ecosystem <- c(Grassland = "#2C7A57", Forest = "#7A4D8E", Cropland = "#B85C4A")

# ============================================================
# 第一步:前提检验
# ============================================================
fit1 <- aov(Shannon ~ Nlevel, data = dat)
shapiro.test(residuals(fit1))   # 正态性(对残差)
leveneTest(Shannon ~ Nlevel, data = dat)   # 方差齐性

# ============================================================
# 第二步:单因素 ANOVA + Tukey 多重比较
# ============================================================
summary(fit1)
TukeyHSD(fit1)

# ============================================================
# 第三步:双因素 ANOVA(主效应 vs 交互模型)
# ============================================================
fit2a <- aov(Shannon ~ Nlevel + Ecosystem, data = dat)
summary(fit2a)          # 主效应模型

fit2b <- aov(Shannon ~ Nlevel * Ecosystem, data = dat)
summary(fit2b)          # 含交互模型

# 注意:交互显著时,不要直接对 Nlevel 做总体 Tukey 比较------
# 更合适的是分别看每个 Ecosystem 内的 simple effects(见进阶部分 emmeans)

# ============================================================
# 第四步:可视化
# ============================================================
# --- Tukey 字母计算函数 ---
tukey_letters <- function(model, fac, data) {
  tk <- TukeyHSD(model)[[fac]]
  lv <- levels(data[[fac]])
  pmat <- matrix(1, length(lv), length(lv), dimnames = list(lv, lv))
  for (pr in rownames(tk)) {
    v <- strsplit(pr, "-")[[1]]
    pmat[v[2], v[1]] <- pmat[v[1], v[2]] <- tk[pr, "p adj"]
  }
  multcompLetters(pmat)$Letters
}

# 单因素箱线图(带 Tukey 字母)
let1 <- tukey_letters(fit1, "Nlevel", dat)
lab1 <- data.frame(Nlevel = names(let1), letters = let1,
                   y = tapply(dat$Shannon, dat$Nlevel, max) + 0.1)
p_oneway <- ggplot(dat, aes(x = Nlevel, y = Shannon, fill = Nlevel)) +
  geom_boxplot(alpha = 0.85, outlier.shape = NA, linewidth = 0.4) +
  geom_jitter(width = 0.12, size = 0.6, alpha = 0.35, color = "grey35") +
  geom_text(data = lab1, aes(x = Nlevel, y = y, label = letters),
            size = 3, fontface = "bold") +
  scale_fill_manual(values = cols_nlevel) +
  labs(x = "Nitrogen addition level", y = "Shannon diversity index") +
  theme_pub() +
  theme(legend.position = "none")

# 双因素分面箱线图(每面板 Tukey 字母)
let2 <- by(dat, dat$Ecosystem, function(d) {
  m <- aov(Shannon ~ Nlevel, data = d)
  tukey_letters(m, "Nlevel", d)
})
lab2 <- do.call(rbind, lapply(names(let2), function(ec) {
  data.frame(Ecosystem = ec, Nlevel = names(let2[[ec]]), letters = let2[[ec]],
             y = tapply(dat$Shannon[dat$Ecosystem == ec],
                        dat$Nlevel[dat$Ecosystem == ec], max) + 0.12)
}))
p_twoway <- ggplot(dat, aes(x = Nlevel, y = Shannon, fill = Nlevel)) +
  geom_boxplot(alpha = 0.85, outlier.shape = NA, linewidth = 0.4) +
  geom_jitter(width = 0.12, size = 0.6, alpha = 0.35, color = "grey35") +
  geom_text(data = lab2, aes(x = Nlevel, y = y, label = letters),
            size = 3, fontface = "bold") +
  facet_wrap(~ Ecosystem, ncol = 3) +
  scale_fill_manual(values = cols_nlevel) +
  labs(x = "Nitrogen addition level", y = "Shannon diversity index") +
  theme_pub() +
  theme(legend.position = "none")

# 交互效应图
mu <- aggregate(Shannon ~ Nlevel + Ecosystem, data = dat, FUN = mean)
p_interact <- ggplot(mu, aes(x = Nlevel, y = Shannon,
                             group = Ecosystem, color = Ecosystem)) +
  geom_point(size = 2.2) +
  geom_line(linewidth = 0.7) +
  scale_color_manual(values = cols_ecosystem) +
  labs(x = "Nitrogen addition level", y = "Mean Shannon diversity index") +
  theme_pub()

# 出图
ggsave("boxplot_oneway.png", p_oneway, width = 110, height = 100, units = "mm", dpi = 300)
ggsave("boxplot_twoway.png", p_twoway, width = 183, height = 90, units = "mm", dpi = 300)
ggsave("interaction_plot.png", p_interact, width = 120, height = 100, units = "mm", dpi = 300)

替换成自己的数据:

  1. 准备 CSV 文件:前面的列是因子列(分类变量),最后一列是连续响应变量
  2. 将因子列转为 factor,如 dat$Group <- factor(dat$Group)
  3. 替换 read.csv() 中的文件名和公式中的变量名,如 aov(value ~ Group)
  4. 若单因素方差齐性不满足,改用 oneway.test(Shannon ~ Nlevel, data = dat)(Welch ANOVA);双因素设计方差不齐时需按研究设计选择稳健方法

七、实用技巧总结

aov 公式写法速查:

公式 含义
aov(y ~ A) 单因素 ANOVA
aov(y ~ A + B) 双因素主效应模型
aov(y ~ A * B) 双因素含交互模型(等价于 A + B + A:B

常见注意事项:

  • 前提检验要验残差,不是原始数据。 对原始数据做 Shapiro-Wilk 常误报非正态,ANOVA 的正态性假设针对的是模型残差而非观测值本身
  • 前提检验要对最终模型做。 单因素和双因素是不同的模型,每个模型都应做残差诊断,不能"检验一次就一劳永逸"
  • 方差齐性用 Levene 而非 Bartlett。 car::leveneTest() 默认以中位数为中心(Brown-Forsythe 型),对非正态数据通常比 Bartlett 更稳健
  • "p > 0.05"是"没有证据拒绝",不是"证明成立"。 前提检验的零假设是"假设成立",p>0.05 只是不拒绝;样本量很大时 Shapiro 会对微小偏离敏感,应结合 Q-Q 图综合判断
  • 显著后的定位依赖事后比较。 ANOVA 显著只说明"有差异",具体哪两组不同必须看 TukeyHSD 等事后检验
  • Tukey 字母是常见标注方式之一。multcompView::multcompLetters() 把事后检验结果转成 a/b/c 字母标注在箱线图上,字母不同即差异显著,是生态/农业论文中常见做法
  • 剂量梯度用渐变配色。 处理水平有递增语义时(如氮添加 0/低/高),用同一色系由浅到深比用不同色相更能传递"剂量递增"的含义
  • 交互显著时慎谈主效应。 交互显著意味着一个因子的效应依赖另一因子,此时主效应需在交互背景下解读;交互显著后的组间比较宜用 emmeans 做 simple effects
  • 注意伪重复(pseudoreplication)。 若多个样方来自同一个样地/样点(嵌套或重复测量结构),它们不是完全独立的重复,普通 ANOVA 会高估样本量。此时需改用混合效应模型,如 lmer(Shannon ~ Nlevel * Ecosystem + (1 | Site), data = dat)
  • 交互不显著不等于必须删交互项。 是否简化加性模型应结合研究设计与预设假设判断,不能仅凭 p 值机械删项
  • 方差不齐要按设计处理。 单因素用 Welch ANOVA(oneway.test());双因素不能简单套 Kruskal-Wallis,需按设计选稳健/置换/广义/混合模型

如果对你有帮助,欢迎 点赞 + 收藏。欢迎评论区交流 👋

相关推荐
十三画者2 小时前
【文献分享】CANVAS:基于细胞构架与邻域信息的组织病理学虚拟空间肿瘤分析
人工智能·机器学习·数据挖掘·数据分析·数据可视化
数模竞赛Paid answer6 小时前
2019年深圳杯数学建模A题深圳居民健康水平评估与测控模型研究解题全过程文档及程序
数学建模·数据分析·深圳杯数学建模挑战赛
数模竞赛Paid answer1 天前
2025年五一杯数学建模A题支路车流量推测问题求解全过程论文及程序
数学建模·数据分析·五一杯
cui_ruicheng1 天前
Python数据分析(十四):Pandas 数据可视化
python·信息可视化·数据分析·pandas
leisoo80971 天前
筹码分布数据分析实战:用Python构建主力建仓成本分析系统
python·数据挖掘·数据分析
编程风暴1 天前
零基础数据分析项目模板+5条避坑红线
java·数据挖掘·数据分析
观远数据1 天前
Excel、自研、还是换BI?产品VP拆解三条数据分析路线的隐性成本
大数据·数据分析·excel
隔窗听雨眠2 天前
AI重塑数据分析:从SQL执行者到智能基础设施构建者
人工智能·sql·数据分析
大数据魔法师2 天前
DrissionPage 从入门到实战:一站式Python爬虫与网页自动化教程
爬虫·python·数据分析