7  单因素组间方差分析

Modified

August 23, 2026

7.1 计算框架

因子A有\(A_1,A_2,...,A_k\)共k个水平

总变异total sum of squares

\[ SS_T=\sum_{j=1}^{k}\sum_{i=1}^{n_j}X_{ij}^2-\frac{(\sum_{j=1}^{k}\sum_{i=1}^{n_j}X_{ij})^2}{n}=SS_{组间}+SS_{组内} \]

组间变异 between groups sum of squares \[ SS_{组间}=\sum_{j=1}^{k}\frac{(\sum_{i=1}^{n_j}X_{ij})^2}{n_j}-\frac{(\sum_{j=1}^{k}\sum_{i=1}^{n_j}X_{ij})^2}{n} \]

自由度\(\nu=n-1,\nu_{组间}=k-1,\nu_{组内}=\sum_{j=1}^{k}(n_j-1)=n-k\)

Between groups mean square \(MS_{组间}=\frac{SS_{组间}}{k-1}\)

Within groups mean square \(MS_{组内}=\frac{SS_{组内}}{n-k}\)

\[H_0:\mu_1=\mu_2=...=\mu_k\]

\[ \frac{SS_T}{\sigma^2}\sim \chi^2(\nu),\nu=n-1 \] \[ \frac{SS_{组内}}{\sigma^2}\sim \chi^2(\nu),\nu=n-k \] 因此,

\[ \frac{SS_{组间}}{\sigma^2}=\frac{SS_T}{\sigma^2}-\frac{SS_{组内}}{\sigma^2}\ \ \ \sim \chi^2(\nu),\nu=k-1 \]

检验统计量

\[ F=\frac{\frac{SS_{组间}}{(k-1)\sigma^2}}{\frac{SS_{组内}}{(n-k)\sigma^2}}=\frac{\frac{SS_{组间}}{k-1}}{\frac{SS_{组内}}{n-k}}=\frac{MS_{组间}}{MS_{组内}}\ \ \ \sim \chi^2(\nu),\nu=k-1 \]

7.2 数据来源

2017.临床研究中的统计分析和图形表达实例详解 第2版

Show the code
# 宽格式数据框(和原表格一致)
library(tidyverse)
df_wide <- data.frame(
  normal = c(332.96, 297.64, 312.57, 295.47, 284.25, 307.97, 292.12, 244.61, 261.46, 286.46, 322.49, 282.42),
  middle = c(253.21, 235.87, 269.30, 258.90, 254.39, 200.87, 227.79, 237.05, 216.85, 238.03, 238.19, 243.49),
  high = c(232.55, 217.71, 216.15, 220.72, 219.46, 247.47, 280.75, 196.01, 208.24, 198.41, 240.35, 219.56)
)
df_long <- df_wide |> pivot_longer(cols = everything(),
                              names_to = "level",
                              values_to = "value")

7.3 R实现

7.3.1 stats::aov()

Show the code

df_long$level <- factor(df_long$level)
df_aov <- aov(value~level,data = df_long)

anova(df_aov)
#> Analysis of Variance Table
#> 
#> Response: value
#>           Df Sum Sq Mean Sq F value    Pr(>F)    
#> level      2  31292   15646  31.355 2.343e-08 ***
#> Residuals 33  16467     499                      
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

7.3.2 stats::lm()

Show the code
lm_aov <- lm(formula = value ~  level, data = df_long)
lm_aov
#> 
#> Call:
#> lm(formula = value ~ level, data = df_long)
#> 
#> Coefficients:
#> (Intercept)  levelmiddle  levelnormal  
#>      224.78        14.71        68.59

anova(lm_aov)
#> Analysis of Variance Table
#> 
#> Response: value
#>           Df Sum Sq Mean Sq F value    Pr(>F)    
#> level      2  31292   15646  31.355 2.343e-08 ***
#> Residuals 33  16467     499                      
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

model.matrix(~ level, data = df_long)
#>    (Intercept) levelmiddle levelnormal
#> 1            1           0           1
#> 2            1           1           0
#> 3            1           0           0
#> 4            1           0           1
#> 5            1           1           0
#> 6            1           0           0
#> 7            1           0           1
#> 8            1           1           0
#> 9            1           0           0
#> 10           1           0           1
#> 11           1           1           0
#> 12           1           0           0
#> 13           1           0           1
#> 14           1           1           0
#> 15           1           0           0
#> 16           1           0           1
#> 17           1           1           0
#> 18           1           0           0
#> 19           1           0           1
#> 20           1           1           0
#> 21           1           0           0
#> 22           1           0           1
#> 23           1           1           0
#> 24           1           0           0
#> 25           1           0           1
#> 26           1           1           0
#> 27           1           0           0
#> 28           1           0           1
#> 29           1           1           0
#> 30           1           0           0
#> 31           1           0           1
#> 32           1           1           0
#> 33           1           0           0
#> 34           1           0           1
#> 35           1           1           0
#> 36           1           0           0
#> attr(,"assign")
#> [1] 0 1 1
#> attr(,"contrasts")
#> attr(,"contrasts")$level
#> [1] "contr.treatment"
lm(formula = value ~ 0 + level, data = df_long)
#> 
#> Call:
#> lm(formula = value ~ 0 + level, data = df_long)
#> 
#> Coefficients:
#>   levelhigh  levelmiddle  levelnormal  
#>       224.8        239.5        293.4
model.matrix(~ 0 + level, data = df_long)
#>    levelhigh levelmiddle levelnormal
#> 1          0           0           1
#> 2          0           1           0
#> 3          1           0           0
#> 4          0           0           1
#> 5          0           1           0
#> 6          1           0           0
#> 7          0           0           1
#> 8          0           1           0
#> 9          1           0           0
#> 10         0           0           1
#> 11         0           1           0
#> 12         1           0           0
#> 13         0           0           1
#> 14         0           1           0
#> 15         1           0           0
#> 16         0           0           1
#> 17         0           1           0
#> 18         1           0           0
#> 19         0           0           1
#> 20         0           1           0
#> 21         1           0           0
#> 22         0           0           1
#> 23         0           1           0
#> 24         1           0           0
#> 25         0           0           1
#> 26         0           1           0
#> 27         1           0           0
#> 28         0           0           1
#> 29         0           1           0
#> 30         1           0           0
#> 31         0           0           1
#> 32         0           1           0
#> 33         1           0           0
#> 34         0           0           1
#> 35         0           1           0
#> 36         1           0           0
#> attr(,"assign")
#> [1] 1 1 1
#> attr(,"contrasts")
#> attr(,"contrasts")$level
#> [1] "contr.treatment"

7.3.3 ez::ezANOVA()

Show the code
df_long <- df_long |> rowid_to_column(var = "id") |> 
    relocate(id,.before = 1) |> mutate(id=factor(id))

ez::ezANOVA(data = df_long,
            dv = value,
            wid = id,
            between = level,
            type = 3,
            detailed = T)
#> $ANOVA
#>        Effect DFn DFd       SSn      SSd          F            p p<.05
#> 1 (Intercept)   1  33 2296103.8 16466.87 4601.44760 5.097646e-37     *
#> 2       level   2  33   31291.8 16466.87   31.35476 2.342720e-08     *
#>         ges
#> 1 0.9928794
#> 2 0.6552067
#> 
#> $`Levene's Test for Homogeneity of Variance`
#>   DFn DFd      SSn      SSd         F         p p<.05
#> 1   2  33 135.1174 7847.544 0.2840937 0.7545184

7.4 前提假设

https://www.statmethods.net/stats/rdiagnostics.html

7.4.1 独立性,正态性

7.4.2 方差齐性

http://www.cookbook-r.com/Statistical_analysis/Homogeneity_of_variance/

7.4.2.1 Bartlett’s test

数据满足正态性

比较每一组方差的加权算术均值和几何均值。

\[ H_0:\sigma_1^2=\sigma_2^2=...=\sigma_k^2 \] 当样本量\(n_j\)≥5时,检验统计量(各组样本量相等)

\[ B=\frac{(n-1)[kln\bar S^2-\sum_{j=1}^{k}lnS_j^2]}{1+\frac{k+1}{3k(n-1)}} \sim\ \chi^2(\nu)\ ,\nu=k-1 \] 其中n是每一组的样本量,\(S_j^2\)是某一组的样本方差,\(\bar S^2\)是所有k个组样本方差的平均值。

当各组样本量不等时,

\[ B=\frac{\sum_{j=1}^{k}(n_j-1)ln\frac{\bar S^2}{S_j^2}}{1+\frac{1}{3(k-1)}(\sum_{j=1}^{k}\frac {1}{n_j-1}-\frac{1}{\sum_{j=1}^{k}(n_j-1)})} \sim\ \chi^2(\nu)\ ,\nu=k-1 \] 其中\(h_j\)是某一组的样本量,\(\bar S^2=(\sum_{j=1}^{k}(n_j-1)S_j^2)/(\sum_{j=1}^{k}(n_j-1))\)是所有k个组样本方差的加权平均值。

Show the code
bartlett.test(value~level,data = df_long)
#> 
#>  Bartlett test of homogeneity of variances
#> 
#> data:  value by level
#> Bartlett's K-squared = 0.83651, df = 2, p-value = 0.6582

7.4.2.2 Levene’s test

数据不满足正态性

  1. k个随机样本是独立的
  2. 随机变量X是连续的
Show the code
car::leveneTest(value~level,data = df_long,center = mean)  # 同SPSS
#> Levene's Test for Homogeneity of Variance (center = mean)
#>       Df F value Pr(>F)
#> group  2  0.3195 0.7287
#>       33

Levene 变换:

\[ Z_{ij}=|X_{ij}-\bar X_{.\ j}|(i=1,2...,n_j;\ j=1,2,...,k) \]

检验统计量(基于变换后的F检验)

\[ W=\frac{MS_{组间}}{MS_{组内}}=\frac{\sum_jn_j(\bar Z_{.j}-\bar Z_{..})^2/(k-1)}{\sum_j\sum_i(Z_{ij}-\bar Z_{.j})^2/(n-k)} \sim F(\nu_1,\nu_2) \ \ \ \ \nu_1=k-1,\nu_2=n-k \]

7.5 事后比较(Post hoc)

成对比较的数量 \(N=\frac{k!}{2!(k-2)!},k≥3\),导致犯第Ⅰ类错误的概率迅速增加,\([1-(1-\alpha)^N]\)

7.5.0.1 Tukey’s test

Tukey’s test 也被称为Tukey’s honestly significant difference (Tukey’s HSD) test。

  1. k个均值从大到小排列;
  2. 均值最大的组依次与均值最小,第二小,……,第二大比较;
  3. 均值第二大的组以同样的方式比较;
  4. 以此类推
  5. 在各组样本量相等的情况下,如果在两个均值之间未发现显著差异,则推断这两个均值所包含的任何均值之间不存在显著差异,并且不再检验所包含均值之间的差异。

studentized range statistic \(q=\frac{\bar X_{max}-\bar X_{min}}{S_{\bar X_{max}-\bar X_{min}}}\),其中\(S_{\bar X_{max}-\bar X_{min}}=\sqrt{\frac{MS_{组内}}{n}}\)\(n\)是每一个治疗组的样本量。

如果各组样本量不等,则\(S_{\bar X_{max}-\bar X_{min}}=\sqrt{\frac{MS_{组内}}{2}(\frac{1}{n_i}+\frac{1}{n_j})}\)

检验统计量

\[ HSD=q_{(k,\nu_{组内}),1-\alpha} \times S_{\bar X_{max}-\bar X_{min}},\nu_{组内}=k(n_j-1) \]

对于任意i,j且\(\bar X_i>\bar X_j\),如果\(\bar X_i-\bar X_j>HSD\),那么拒绝\(H_0\),说明这两组存在显著差异。

\[ H_0:\mu_i=\mu_j(i≠j) \]

Show the code
posthoc_tukey <- TukeyHSD(df_aov)
posthoc_tukey
#>   Tukey multiple comparisons of means
#>     95% family-wise confidence level
#> 
#> Fit: aov(formula = value ~ level, data = df_long)
#> 
#> $level
#>                   diff       lwr      upr     p adj
#> middle-high   14.71333 -7.664142 37.09081 0.2542971
#> normal-high   68.58667 46.209192 90.96414 0.0000000
#> normal-middle 53.87333 31.495858 76.25081 0.0000037


library(multcomp)
posthoc_tukey_glht <- glht(df_aov, linfct = mcp(level = "Tukey"))
summary(posthoc_tukey_glht)
#> 
#>   Simultaneous Tests for General Linear Hypotheses
#> 
#> Multiple Comparisons of Means: Tukey Contrasts
#> 
#> 
#> Fit: aov(formula = value ~ level, data = df_long)
#> 
#> Linear Hypotheses:
#>                      Estimate Std. Error t value Pr(>|t|)    
#> middle - high == 0      14.71       9.12   1.613    0.254    
#> normal - high == 0      68.59       9.12   7.521   <0.001 ***
#> normal - middle == 0    53.87       9.12   5.907   <0.001 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> (Adjusted p values reported -- single-step method)

7.5.0.2 LSD-t test

least significant difference t-test

挑选任意感兴趣的两组进行比较

\[ H_0:\mu_i=\mu_j(i≠j) \]

\[ LSD-t=\frac{\bar X_i-\bar X_j}{\sqrt{MS_{组内}(\frac{1}{n_i}+\frac{1}{n_j})}} \sim t(\nu) \ ,\ \nu=\nu_{组内}=n-k,a=k \]

Show the code
library(agricolae)
# LSD 检验
lsd_result <- LSD.test(
  df_aov,        # 方差分析模型
  "level",   # 分组变量
  p.adj = "none",  # LSD 不校正(真实 LSD 定义)
  console = TRUE   # 直接输出结果
)
#> 
#> Study: df_aov ~ "level"
#> 
#> LSD t Test for value 
#> 
#> Mean Square Error:  498.996 
#> 
#> level,  means and individual ( 95 %) CI
#> 
#>           value      std  r       se      LCL      UCL    Min    Max      Q25
#> high   224.7817 23.24461 12 6.448488 211.6621 237.9012 196.01 280.75 214.1725
#> middle 239.4950 18.72159 12 6.448488 226.3755 252.6145 200.87 269.30 233.8500
#> normal 293.3683 24.62068 12 6.448488 280.2488 306.4879 244.61 332.96 283.7925
#>            Q50     Q75
#> high   219.510 234.500
#> middle 238.110 253.505
#> normal 293.795 309.120
#> 
#> Alpha: 0.05 ; DF Error: 33
#> Critical Value of t: 2.034515 
#> 
#> least Significant Difference: 18.55384 
#> 
#> Treatments with the same letter are not significantly different.
#> 
#>           value groups
#> normal 293.3683      a
#> middle 239.4950      b
#> high   224.7817      b

# LSD 值 = 18.55 (两组均值差值 >18.55 即为显著)
# 正常钙组 与 中剂量组:字母不同(a vs b)→ 差异极显著(P<0.05)
# 正常钙组 与 高剂量组:字母不同(a vs b)→ 差异极显著(P<0.05)
# 中剂量组 与 高剂量组:字母相同(b vs b)→ 差异不显著(P>0.05)

7.5.0.3 Dunnett’s test

Dunnett’s test也称为q’-test,是两独立样本t-test的一种修正。Dunnett’s test 假设数据符合正态分布,并且各组的方差相等。 控制对照组(C)与其他每个实验组(T)比较。

\(H_0:\mu_C=\mu_T\)

\[ q'=\frac{\bar X_T-\bar X_C}{\sqrt{MS_{组内}(\frac{1}{n_T}+\frac{1}{n_C})}} \sim q'(\nu,a) \ \ \nu=\nu_{组内},a=k \]

临界值 \(q'_{(a,\nu_E),1-\alpha/2}\)

Show the code
library(multcomp)
dunnett_result <- glht(df_aov, linfct =mcp(level =c("middle - normal = 0", "high - normal = 0")))

# 查看 Dunnett's test 结果
summary(dunnett_result)
#> 
#>   Simultaneous Tests for General Linear Hypotheses
#> 
#> Multiple Comparisons of Means: User-defined Contrasts
#> 
#> 
#> Fit: aov(formula = value ~ level, data = df_long)
#> 
#> Linear Hypotheses:
#>                      Estimate Std. Error t value Pr(>|t|)    
#> middle - normal == 0   -53.87       9.12  -5.907 2.51e-06 ***
#> high - normal == 0     -68.59       9.12  -7.521 2.38e-08 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> (Adjusted p values reported -- single-step method)

7.6 Welch’s ANOVA Test

Show the code
welch_aov <- oneway.test(value ~ level, data = df_long,var.equal = F)
welch_aov
#> 
#>  One-way analysis of means (not assuming equal variances)
#> 
#> data:  value and level
#> F = 26.721, num df = 2.000, denom df = 21.665, p-value = 1.417e-06

library(rstatix)
df_long |> welch_anova_test(value ~ level)
#> # A tibble: 1 × 7
#>   .y.       n statistic   DFn   DFd          p method     
#> * <chr> <int>     <dbl> <dbl> <dbl>      <dbl> <chr>      
#> 1 value    36      26.7     2  21.7 0.00000142 Welch ANOVA



# 方差不齐时,两两 Welch t 检验(不合并方差,输出p值)
pairwise.t.test(
  df_long$value, 
  df_long$level, 
  p.adjust.method = "none",  # 不校正 = LSD风格
  pool.sd = FALSE            # 不使用合并方差,即两两Welch t
)
#> 
#>  Pairwise comparisons using t tests with non-pooled SD 
#> 
#> data:  df_long$value and df_long$level 
#> 
#>        high    middle 
#> middle 0.1     -      
#> normal 4.9e-07 6.0e-06
#> 
#> P value adjustment method: none

7.7 数据变换

当方差分析的正态性假设或方差齐性假设不为真时,通常使用(1)数据变换方法;(2)非参数检验方法 比较均值差异

7.7.1 平方根变换

当每个水平组的方差与均值成比例,尤其是样本来自泊松分布

\[ Y=\sqrt{X} \]

当数据中有零或非常小的值时,

\[ Y=\sqrt{X+a} \ \ \ \ a=0.5或0.1 \]

7.7.2 对数变换

当数据方差不齐且每个水平组标准差与均值成比例时

\[ Y=\log{X} \ \ \ \ base=e或10 \]

当数据中有零或负值时,

\[ Y=\log{(X+a)} \ \ \ \ a为实数,使得X+a>0 \]

7.7.3 反正弦平方根变换

率,服从二项分布\(B(n,\pi)\)

\[ Y=\arcsin {\sqrt{\pi}} \ \ \ \ \]