2  经典数据集分析

R 是一种专门为统计分析和数据可视化设计的编程语言和软件环境。它就像 Excel 的公式、SPSS 的统计模块和 Origin 的绘图工具的结合体,但更强大、更灵活。与点点鼠标不同,R 主要通过“写代码”来操作数据和做分析,这样不仅能处理更大规模的数据,还能把整个分析过程保存下来、重复运行、自动化。R 自带了很多统计方法(回归、方差分析、时间序列等),还能画出质量出版级的图形,并且有一个庞大的用户社区不断开发扩展包。如果 Excel/SPSS/Origin 是工具箱里的单个工具,那么 R 更像是一个开放的实验室,你可以自由搭建属于自己的工具组合。

因此,在使用 R 语言的时候,不仅需要安装 R 语言本体,还需要根据需要安装相应的软件包。安装他们的过程这里不再赘述,这里我们使用的主要是下面一些软件包,使用时首先通过 library() 函数导入,然后就可以调用相应软件包里面的功能。在下面的代码中,他们各自的功能以代码注释的形式写在 library() 之上。

# datasets 包含了我们下面要使用的数据集
library(datasets)

# dplyr 和 tidyr 用于对数据进行清洗和格式化
library(dplyr)
library(tidyr)

# ggplot2 用于数据可视化
library(ggplot2)

showtext::showtext.auto()

本节,我们将以 4 个 R 语言安装后默认自带的数据集为例,展示使用 R 语言进行数据分析、假设检验、模型构建以及数据可视化方面的知识。

2.1 认识数据集

本节一共使用了 3 个数据集,分别是 npkChickWeightPlantGrowth,他们是 R 中最经典、最常用于农业科学研究的经典数据。

2.1.1 npk

npk 数据集来源于经典的田间施肥试验,它以数据框(data.frame)的形式存储,共有 24 行、5 列,记录了 6 个区组(block)下不同氮肥(N)、磷肥(P)、钾肥(K)处理组合对作物产量(yield, 单位为英担/英亩)的影响,其中 N、P、K 均以 0/1 表示未施或施用。该试验采用 2³ 因子设计并结合区组设计,属于典型的裂区田间试验,常用于方差分析(ANOVA)、因子设计与交互作用分析的教学示例。对于农业研究而言,它的意义在于揭示氮、磷、钾三种肥料对作物产量的主效应及交互作用效应,从而为科学施肥和提高产量提供统计依据。

# 载入数据集
data(npk)

# 查看数据库的行数和列数
dim(npk)
#> [1] 24  5
# 统计不同处理下的观测数量
npk |> count(N, P, K)
#>   N P K n
#> 1 0 0 0 3
#> 2 0 0 1 3
#> 3 0 1 0 3
#> 4 0 1 1 3
#> 5 1 0 0 3
#> 6 1 0 1 3
#> 7 1 1 0 3
#> 8 1 1 1 3

上面使用 count()npk 数据中氮 (N)、磷 (P)、钾 (K) 三个因子的处理组合计数。其结果表明 npk 数据集中氮 (N)、磷 (P)、钾 (K) 三个因子的所有 8 种组合(2³ 设计)都出现过,每种组合各有 3 次观测,因此整体上是一个平衡的因子设计。

2.1.2 ChickWeight

ChickWeight 数据集记录了 50 只小鸡在 0–21 天日龄阶段的体重变化,共 578 条观测,包含小鸡编号、日龄、体重和饲料类型四个变量。实验设计为将小鸡分配到 4 种饲料处理中,并进行纵向重复测量,因此非常适合用于生长曲线拟合、纵向数据与重复测量分析及混合效应模型的教学示例。在农业与畜牧领域,它的意义在于研究不同饲料配方对家禽生长性能的影响。

# 载入数据集
data(ChickWeight)

# 查看数据库的行数和列数
dim(ChickWeight)
#> [1] 578   4
# 查看数据概要
summary(ChickWeight)
#>      weight           Time           Chick     Diet   
#>  Min.   : 35.0   Min.   : 0.00   13     : 12   1:220  
#>  1st Qu.: 63.0   1st Qu.: 4.00   9      : 12   2:120  
#>  Median :103.0   Median :10.00   20     : 12   3:120  
#>  Mean   :121.8   Mean   :10.72   10     : 12   4:118  
#>  3rd Qu.:163.8   3rd Qu.:16.00   17     : 12          
#>  Max.   :373.0   Max.   :21.00   19     : 12          
#>                                  (Other):506

这里使用 summary(ChickWeight) 来查看数据的概要。其结果展示了数据的基本分布情况:

  • 体重(weight):从最小 35 克到最大 373 克,平均值约 122 克,中位数 103 克,说明小鸡体重差异较大,存在显著增长趋势。
  • 日龄(Time):记录从 0 天到 21 天,平均约 10.7 天,四分位数显示数据大致均匀分布在整个生长期。
  • 小鸡编号(Chick):共有 50 只小鸡,大多数编号各自有 12 条观测,说明基本上每只小鸡都被连续跟踪测量。
  • 饲料类型(Diet):分为 4 种,其中饲料 1 有 220 条观测,饲料 2 和 3 各 120 条,饲料 4 有 118 条,分配大致均衡但不完全相等。

总体上,该数据集清晰反映了不同饲料处理下小鸡在 0–21 天日龄期间的体重变化,为分析生长曲线和饲料效果提供了基础。

2.1.3 PlantGrowth

PlantGrowth 数据集记录了 30 株植物的干重及其所属实验组别,共 2 列变量:植物干重(weight)和实验组(group,包括对照组 ctrl 以及处理组 trt1trt2)。实验采用完全随机设计,每组 10 株植物,用于比较两种处理对植物生长的影响。该数据集常用于 ANOVA 教学、组间均值比较和 Tukey 检验,是农业实验设计的典型入门案例。

# 载入数据集
data(PlantGrowth)

# 查看数据库的行数和列数
dim(PlantGrowth)
#> [1] 30  2
# 计算不同处理下的平均值和标准差
PlantGrowth |> group_by(group) |> 
  summarise(mean = mean(weight),
            std = sd(weight))
#> # A tibble: 3 × 3
#>   group  mean   std
#>   <fct> <dbl> <dbl>
#> 1 ctrl   5.03 0.583
#> 2 trt1   4.66 0.794
#> 3 trt2   5.53 0.443

上面使用了 dplyr 包提供的两个函数,计算了 PlantGrowth 数据集中三组处理下植物干重的均值和标准差。结果显示,对照组(ctrl)平均干重约 5.03 克,标准差 0.58;处理组 1(trt1)平均 4.66 克,标准差 0.79;处理组 2(trt2)平均 5.53 克,标准差 0.44。可以看出,不同处理对植物干重有一定影响,其中 trt2 的平均干重最高且波动最小,trt1 平均干重最低且变异较大,为后续 ANOVA 和组间比较分析提供了基础。

2.2 数据分析实战

下面,我们将基于上述 3 个数据集,展示如何使用 R 语言及相关软件包进行数据分析和可视化。

2.2.1 npk:因子设计、ANOVA 与交互作用

2.2.1.1 了解数据集

npk 数据集是 R 自带的一个作物实验数据集,结构如下:

head(npk)
#>   block N P K yield
#> 1     1 0 1 1  49.5
#> 2     1 1 1 0  62.8
#> 3     1 0 0 0  46.8
#> 4     1 1 0 1  57.0
#> 5     2 1 0 0  59.8
#> 6     2 1 1 1  58.5
  • block:区组(随机区组设计的块)
  • NPK:氮、磷、钾三种肥料水平(0=不施,1=施)
  • yield:作物产量
str(npk)
#> 'data.frame':    24 obs. of  5 variables:
#>  $ block: Factor w/ 6 levels "1","2","3","4",..: 1 1 1 1 2 2 2 2 3 3 ...
#>  $ N    : Factor w/ 2 levels "0","1": 1 2 1 2 2 2 1 1 1 2 ...
#>  $ P    : Factor w/ 2 levels "0","1": 2 2 1 1 1 2 1 2 2 2 ...
#>  $ K    : Factor w/ 2 levels "0","1": 2 1 1 2 1 2 2 1 1 2 ...
#>  $ yield: num  49.5 62.8 46.8 57 59.8 58.5 55.5 56 62.8 55.8 ...

还可以用 str(npk) 查看数据结构。可以看出:

  • 'data.frame': 24 obs. of 5 variables 说明 npk 是一个 数据框(data.frame),总共有 24 个观测值(行),每个观测值记录一个实验处理下的结果,有 5 个变量(列)。

  • $ block: Factor w/ 6 levels "1","2","3","4",..: 1 1 1 1 2 2 2 2 3 3 ...

    • block因子(factor),共有 6 个水平(1 到 6),表示实验的随机区组。
    • 数字序列 1 1 1 1 2 2 2 2 ... 表示前 4 个观测值属于第 1 块,接下来 4 个属于第 2 块,以此类推。
  • $ N: Factor w/ 2 levels "0","1": 1 2 1 2 2 2 1 1 1 2 ...

    • N氮肥水平因子,有两个水平:0(不施氮)和 1(施氮)。
    • 序列 1 2 1 2 ... 表示第一个观测值是水平 0(因为 factor 编码中 1 对应第一个水平 “0”),第二个是水平 1,即施氮。
  • $ P: Factor w/ 2 levels "0","1": 2 2 1 1 1 2 1 2 2 2 ...

    • P磷肥水平因子,也有两个水平 0/1。
    • 序列表示每个观测值对应的磷肥处理水平。
  • $ K: Factor w/ 2 levels "0","1": 2 1 1 2 1 2 2 1 1 2 ...

    • K钾肥水平因子,同样有两个水平。
    • 序列显示每个观测值的钾肥处理。
  • $ yield: num 49.5 62.8 46.8 57 59.8 58.5 55.5 56 62.8 55.8 ...

    • yield作物产量,数值型(numeric),记录每个实验处理的结果。
    • 这些数值可以用于后续的 ANOVA 分析,来研究 N、P、K 以及它们交互作用对产量的影响。

综上,npk 是一个典型的 2×2×2 因子实验数据集,在 6 个区组中,每个因子(N、P、K)有 2 个水平,共 24 条观测。yield 是因变量,可以用它来做单因素或多因素 ANOVA,并分析主效应和交互作用。

2.2.1.2 因子设计

NPK 是因子(factor),每个有 2 个水平(0 或 1),所以这是一个 2×2×2 的全因子设计。正如前面 count() 的结果所示,每种组合各有 3 次观测,是一个平衡的因子设计。

接下来,我们计算各组合的平均产量来理解因子的效应:

npk |>
  group_by(N, P, K) |>
  summarise(mean_yield = mean(yield))
#> # A tibble: 8 × 4
#> # Groups:   N, P [4]
#>   N     P     K     mean_yield
#>   <fct> <fct> <fct>      <dbl>
#> 1 0     0     0           51.4
#> 2 0     0     1           52  
#> 3 0     1     0           54.3
#> 4 0     1     1           50.5
#> 5 1     0     0           63.8
#> 6 1     0     1           54.7
#> 7 1     1     0           57.9
#> 8 1     1     1           54.4

2.2.1.3 单因素 ANOVA

先看单因素 N 的效应:

aov_N <- aov(yield ~ N, data = npk)
summary(aov_N)
#>             Df Sum Sq Mean Sq F value Pr(>F)  
#> N            1  189.3  189.28   6.061 0.0221 *
#> Residuals   22  687.1   31.23                 
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
  • yield ~ N 表示分析的是产量受氮肥水平的影响
  • summary() 会输出 F 值和 p 值,用于判断氮肥是否显著影响产量

单因素 ANOVA 的结果中:

  • Df:自由度(Degrees of Freedom)
    • N 的自由度 = 1(因为 N 有 2 个水平,自由度 = 水平数 - 1)
    • Residuals(残差)的自由度 = 22(总观测数 24 - 因子自由度 1 = 23?这里减去 1 是因为没有截距吗?实际上 R 默认还减去因子 1 个自由度,残差 = 24-2=22)
  • Sum Sq:平方和(Sum of Squares)
    • N 的平方和 = 189.3,表示因子 N 对产量的变异贡献
    • Residuals 的平方和 = 687.1,表示未被 N 解释的变异
  • Mean Sq:均方(Mean Square) = Sum Sq / Df
    • N = 189.28
    • Residuals = 31.23
  • F value = 189.28 / 31.23 ≈ 6.061
    • F 值越大,说明因子对产量的影响越显著
  • Pr(>F) = 0.0221
    • 这是 p 值,表示在零假设(N 对产量没有影响)下观察到 F ≥ 6.061 的概率
    • 0.0221 < 0.05,说明 N 对产量的影响 显著
  • Signif. codes: * 表示显著性水平
    • * → p < 0.05

这个结果说明,氮肥 N 对作物产量有显著正向或负向影响(具体正负可以看平均产量差异)。

同理可对 PK 做单因素 ANOVA。

# 对施用 P 肥做单因素 ANOVA
aov_P <- aov(yield ~ P, data = npk)
summary(aov_P)
#>             Df Sum Sq Mean Sq F value Pr(>F)
#> P            1    8.4    8.40   0.213  0.649
#> Residuals   22  868.0   39.45
# 对使用 K 肥做单因素 ANOVA
aov_K <- aov(yield ~ K, data = npk)
summary(aov_K)
#>             Df Sum Sq Mean Sq F value Pr(>F)
#> K            1   95.2   95.20   2.681  0.116
#> Residuals   22  781.2   35.51

单因素 ANOVA 也可以写成下面的形式,表示分别分析 N、P、K 对产量的影响,不考虑交互作用。

aov_1way <- aov(yield ~ N + P + K, data = npk)
summary(aov_1way)
#>             Df Sum Sq Mean Sq F value Pr(>F)  
#> N            1  189.3  189.28   6.488 0.0192 *
#> P            1    8.4    8.40   0.288 0.5974  
#> K            1   95.2   95.20   3.263 0.0859 .
#> Residuals   20  583.5   29.17                 
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

2.2.1.4 多因素 ANOVA(主效应 + 交互作用)

单因素 ANOVA 可以帮助我们判断这个因子是否显著,但无法分析交互作用。为了考虑三个肥料之间的交互作用,需要做多因素方差分析。在做多因素 ANOVA 分析时,只需要将上面代码中的加号改成乘号即可。

aov_full <- aov(yield ~ N * P * K, data = npk)
summary(aov_full)
#>             Df Sum Sq Mean Sq F value Pr(>F)  
#> N            1  189.3  189.28   6.161 0.0245 *
#> P            1    8.4    8.40   0.273 0.6082  
#> K            1   95.2   95.20   3.099 0.0975 .
#> N:P          1   21.3   21.28   0.693 0.4175  
#> N:K          1   33.1   33.14   1.078 0.3145  
#> P:K          1    0.5    0.48   0.016 0.9019  
#> N:P:K        1   37.0   37.00   1.204 0.2887  
#> Residuals   16  491.6   30.72                 
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
  • N * P * K 表示 N、P、K 的主效应 + 两两交互作用 + 三重交互作用
  • 输出中会看到每个效应的 F 值和显著性

如果只关注两两交互作用,不考虑三重交互作用,则可以这样写:

aov_2way <- aov(yield ~ (N + P + K)^2, data = npk)
summary(aov_2way)
#>             Df Sum Sq Mean Sq F value Pr(>F)  
#> N            1  189.3  189.28   6.088 0.0245 *
#> P            1    8.4    8.40   0.270 0.6099  
#> K            1   95.2   95.20   3.062 0.0982 .
#> N:P          1   21.3   21.28   0.684 0.4195  
#> N:K          1   33.1   33.14   1.066 0.3164  
#> P:K          1    0.5    0.48   0.015 0.9024  
#> Residuals   17  528.6   31.09                 
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

2.2.1.5 交互作用可视化

交互作用图能帮助理解因子之间的相互影响:

interaction.plot(
  npk$N, npk$P, npk$yield, fun = mean,
  col = c("red","blue"), lty = 1:2,
  trace.label = "P level"
)
图 2.1: NPK数据集中N×P交互作用图

在这幅图上,横轴是 N 的水平,不同颜色表示 P 的水平,纵轴是产量平均值。如果线基本平行,表示没有明显交互作用,即 N 的效应对产量变化不依赖于 P 的水平,这种情况下可以认为 N 和 P 的效应是独立的;如果线明显交叉或斜率不同,则表示存在交互作用,如 N 施肥时产量随 P 的水平变化很大,但不施用 N 时变化小,说明 N、P 有交互作用。

# N × K 交互作用
interaction.plot(
  npk$N, npk$K, npk$yield, fun = mean,
  col = c("green","purple"), lty = 1:2,
  trace.label = "K level",
  xlab = "N level", ylab = "Mean yield",
  main = "N × K interaction"
)

# P × K 交互作用
interaction.plot(
  npk$P, npk$K, npk$yield, fun = mean,
  col = c("orange","brown"), lty = 1:2,
  trace.label = "K level",
  xlab = "P level", ylab = "Mean yield",
  main = "P × K interaction"
)

综上,通过 npk 数据,我们理解了因子设计的概念,并学习了单因素 ANOVA(分析单个因子对产量的影响)及多因素 ANOVA(考虑多个因子及交互作用)的分析方法,并使用 interaction.plot() 方法可视化了因子间的交互作用(理解因子如何互相影响)。所得结果表明,N 肥施用对作物产量的影响是最显著的,K 肥施用的影响次之。N、P 肥之间存在较强的交互作用,但是结果并不显著。总体上,N 肥发挥了主效作用。

2.2.2 ChickWeight:生长曲线、纵向数据与混合效应模型

2.2.2.1 了解数据集

ChickWeight 是 R 自带的 纵向实验数据集,记录了小鸡在不同时间点的体重变化以及饲料类型(Diet)信息。它常用于学习 生长曲线分析、重复测量数据处理和混合效应模型。数据集特点包括:

  • 重复测量:每只小鸡(Chick)在多个时间点测量体重
  • 因子处理:不同的饲料类型(Diet)可能影响生长速率
  • 纵向结构:时间(Time)是连续变量,可分析生长趋势
  • 随机效应:不同个体可能有不同初始体重和生长速率

通过该数据集,可以探索以下分析方法:

  1. 生长曲线可视化:绘制每只小鸡的体重随时间变化的曲线,观察总体趋势和个体差异
  2. 纵向数据分析:处理重复测量数据,考虑时间依赖性和个体间差异
  3. 混合效应模型:使用线性混合效应模型(如 lmer)拟合固定效应(Time、Diet)和随机效应(Chick),分析饲料对生长速率的影响以及个体差异
  4. 交互作用分析:探索 Time × Diet 的交互作用,判断不同饲料是否改变小鸡的生长曲线形状

这个数据集是学习生物学、生长动力学或纵向数据分析的经典示例。

head(ChickWeight)
#>   weight Time Chick Diet
#> 1     42    0     1    1
#> 2     51    2     1    1
#> 3     59    4     1    1
#> 4     64    6     1    1
#> 5     76    8     1    1
#> 6     93   10     1    1

我们来看 head(ChickWeight) 的输出,逐项解释每一列的含义:

  • weight:小鸡体重(数值型,因变量),单位通常为克
  • Time:测量时间(天数),纵向变量,用于分析生长曲线
  • Chick:小鸡编号(因子/分组变量),每只小鸡在不同时间有重复测量
  • Diet:饲料类型(因子),实验处理组,用于分析不同饲料对生长的影响
str(ChickWeight)
#> Classes 'nfnGroupedData', 'nfGroupedData', 'groupedData' and 'data.frame':   578 obs. of  4 variables:
#>  $ weight: num  42 51 59 64 76 93 106 125 149 171 ...
#>  $ Time  : num  0 2 4 6 8 10 12 14 16 18 ...
#>  $ Chick : Ord.factor w/ 50 levels "18"<"16"<"15"<..: 15 15 15 15 15 15 15 15 15 15 ...
#>  $ Diet  : Factor w/ 4 levels "1","2","3","4": 1 1 1 1 1 1 1 1 1 1 ...
#>  - attr(*, "formula")=Class 'formula'  language weight ~ Time | Chick
#>   .. ..- attr(*, ".Environment")=<environment: R_EmptyEnv> 
#>  - attr(*, "outer")=Class 'formula'  language ~Diet
#>   .. ..- attr(*, ".Environment")=<environment: R_EmptyEnv> 
#>  - attr(*, "labels")=List of 2
#>   ..$ x: chr "Time"
#>   ..$ y: chr "Body weight"
#>  - attr(*, "units")=List of 2
#>   ..$ x: chr "(days)"
#>   ..$ y: chr "(gm)"

使用 str() 分析 ChickWeight 的数据定义。可以得出,ChickWeight 是一个带有分组信息的纵向数据框,共 578 条观测、4 个变量。此外,它同时还属于 groupedData 类。该类是一种用于纵向数据分析的格式,公式 weight ~ Time | Chick 表示以每只小鸡为分组,观察体重随时间变化,~Diet 表示外部因子分组。数据还包含标签和单位信息,便于绘图和解释。

2.2.2.2 生长曲线可视化

在分析 ChickWeight 数据时,我们既关心每只小鸡随时间的体重变化曲线,也关心不同饲料类型下的整体生长趋势。下面这段代码使用 ggplot2 绘制出生长曲线图,将个体曲线与各饲料组的平均轨迹叠加,方便比较个体差异与处理效应。

# 各饲料的平均增长轨迹
ggplot(ChickWeight, aes(x = Time, y = weight, group = Chick, color = Diet)) +
  geom_line(alpha = 0.4) +
  stat_summary(aes(group = Diet), fun = mean, geom = "line", linewidth = 1.2) +
  facet_wrap(~Diet) +
  labs(title = "ChickWeight: 生长轨迹(个体 + 饮食均值)", x = "日龄", y = "体重(g)") +
  theme_bw()
图 2.2: ChickWeight 生长轨迹(个体曲线 + 饲料均值)

这段画图代码的思路是:先画出所有个体的曲线来体现小鸡之间的差异,再叠加各饲料组的均值曲线作为整体趋势,最后用分面把不同饲料条件下的生长模式清楚地区分开。这样,个体差异与组间差异在同一张图中都能直观呈现。

对代码里主要函数的作用说明一下:

  • ggplot():建立绘图框架,指定数据集和映射关系 (aes)。这里把 Time 映射到 x 轴,weight 映射到 y 轴,Chick 表示分组曲线,Diet 用来区分颜色。
  • geom_line():画出每只小鸡的生长曲线。alpha = 0.4 让线条半透明,避免个体曲线遮挡过多。
  • stat_summary():对每个 Diet 组计算均值,并画出一条更粗的平均生长曲线,用于突出整体趋势。
  • facet_wrap(~Diet):按饲料分面绘图,把不同饮食条件分到不同小图里,更方便比较。
  • labs():给图加标题和坐标轴标签,解释图形内容。
  • theme_bw():采用黑白主题,使图形更简洁清晰。

从图中我们可以直观地看到:每只小鸡的生长曲线都呈现随时间上升的趋势,但不同小鸡之间存在明显差异,同时,不同饲料组的平均轨迹也表现出一定差别。

2.2.2.3 生长曲线模型

仅靠画图能帮我们直观认识趋势,但如果想定量分析——比如比较饲料组的效果、估计生长速度,或者控制个体差异——就需要用到混合效应模型

因为本数据中小鸡既存在饮食差异(一共 4 种喂养模式),又存在个体差异(小鸡生长曲线不一样),所以推荐选择非线性混合效应模型。因此,下面使用 nlme 软件包来完成模型拟合和预测。

nlmeR 语言中一个功能强大的软件包,它主要用于拟合和分析 非线性混合效应模型(Nonlinear Mixed-Effects Models)nlme 软件包的核心功能是处理那些同时包含固定效应(fixed effects)(这里是喂养模式)和随机效应(random effects)(这里是小鸡个体差异)的复杂数据集。

# 加载软件包
library(nlme)

在生物学研究中,逻辑斯蒂函数常用来描述生长曲线,因此这里需要先定义一个逻辑斯蒂生长模型:

\[ weight = \frac{Asym}{1 + \exp(-(Time - xmid)/scal)} \]

  • Asym:最大体重
  • xmid:拐点时间
  • scal:曲线陡峭度

2.2.2.4 先用 nlsList() 为每只小鸡拟合

# 按小鸡个体分别拟合逻辑斯蒂曲线
fm1 <- nlsList(
  weight ~ SSlogis(Time, Asym, xmid, scal) | Chick,
  data = ChickWeight
)
summary(fm1)
#> Call:
#>   Model: weight ~ SSlogis(Time, Asym, xmid, scal) | Chick 
#>    Data: ChickWeight 
#> 
#> Coefficients:
#>    Asym 
#>      Estimate   Std. Error    t value     Pr(>|t|)
#> 18         NA           NA         NA           NA
#> 16   54.69829     5.290905 10.3381736 3.647390e-05
#> 15   69.43472     3.958350 17.5413296 6.156577e-08
#> 13 1182.09208 18279.651180  0.0646671 9.431953e-01
#> 9   100.05400     3.147814 31.7852282 4.440715e-11
#> 20  245.73628   143.324551  1.7145442 4.968573e-04
#> 10  158.89170    23.596933  6.7335742 1.367084e-07
#> 8   145.11987     8.695426 16.6892195 5.124818e-08
#> 17  193.66184    34.709980  5.5794281 1.597163e-05
#> 19         NA           NA         NA           NA
#> 4   265.78099    51.133277  5.1978087 1.507385e-03
#> 6   168.00722     4.139145 40.5898371 1.113504e-10
#> 11  189.54941     3.495938 54.2199017 2.051045e-10
#> 3   297.09367    30.057094  9.8843111 4.026260e-06
#> 1   937.03020   777.631806  1.2049793 7.516238e-02
#> 12  338.46718    47.155653  7.1776587 4.481032e-04
#> 2   385.76885    64.478009  5.9829522 1.469177e-07
#> 5   265.01074    11.069744 23.9400954 2.505172e-07
#> 14  320.15720    11.952160 26.7865564 2.087418e-09
#> 7   561.86815    66.205044  8.4867876 4.666041e-06
#> 24   72.66406     2.144161 33.8892754 3.820054e-12
#> 30  181.14074    10.907530 16.6069438 1.374039e-08
#> 22  316.31606    93.276213  3.3911760 6.176029e-03
#> 23  212.04214    14.601829 14.5216157 2.773553e-09
#> 27  341.19393    69.121040  4.9361805 6.658799e-05
#> 28  288.89129    17.105288 16.8890044 4.487424e-08
#> 26  585.59907   145.865331  4.0146557 3.355405e-03
#> 25  421.66695    41.296670 10.2106769 3.885517e-07
#> 29         NA           NA         NA           NA
#> 21  364.67747     7.752535 47.0397688 5.366047e-11
#> 33  159.60462     4.215973 37.8571231 1.671131e-10
#> 37  480.84307   231.000985  2.0815629 2.003254e-02
#> 36  272.26995    11.453090 23.7726193 1.599565e-07
#> 31 4523.98451 14375.923494  0.3146917 5.480146e-01
#> 39 8474.33185 45662.516928  0.1855862 8.197631e-01
#> 38 1598.29391  1078.372068  1.4821359 1.770692e-01
#> 32  504.00304    47.052387 10.7115297 5.329751e-07
#> 40  725.22686   126.415679  5.7368427 8.606118e-06
#> 34  643.20081    69.109755  9.3069468 8.410196e-05
#> 35  477.01095    16.732775 28.5075818 1.196093e-10
#> 44  156.92958     6.674004 23.5135590 4.328043e-10
#> 45  250.90646    19.441427 12.9057632 5.727575e-06
#> 43  206.27312     3.290702 62.6836233 2.454218e-12
#> 41  225.55451     8.775271 25.7034249 1.387464e-09
#> 47  243.62492    12.113462 20.1119151 1.316542e-08
#> 49  301.35667    18.633455 16.1728817 4.628648e-08
#> 46  359.00170    36.470204  9.8436989 5.243686e-06
#> 50  390.21471    30.566288 12.7661790 3.718209e-08
#> 42  438.74639    40.836789 10.7439004 6.178713e-07
#> 48  731.18494   124.620227  5.8673054 2.661195e-04
#>    xmid 
#>      Estimate  Std. Error    t value     Pr(>|t|)
#> 18         NA          NA         NA           NA
#> 16 -3.5897381   3.9799561 -0.9019542 1.550498e-01
#> 15 -0.9561391   0.9895737 -0.9662131 4.058495e-02
#> 13 90.7358050 540.5817432  0.1678484 8.533937e-01
#> 9   1.8417545   0.5174225  3.5594788 2.784501e-03
#> 20 21.9203776  15.2515555  1.4372552 1.625834e-03
#> 10  9.9835719   3.0512509  3.2719603 5.489488e-05
#> 8   5.8904819   0.8983804  6.5567794 6.123277e-05
#> 17 11.9207815   3.9711609  3.0018380 1.526269e-03
#> 19         NA          NA         NA           NA
#> 4  16.3938347   3.7810771  4.3357579 4.579035e-03
#> 6   6.1507790   0.3505903 17.5440671 1.829390e-07
#> 11  6.1558869   0.2575715 23.8997179 2.787868e-07
#> 3  14.9024083   1.6510878  9.0258121 8.516255e-06
#> 1  35.2229642  13.8745243  2.5386790 2.182032e-03
#> 12 17.2339305   2.3049489  7.4769253 3.351764e-04
#> 2  18.7592296   2.8816161  6.5099683 7.053447e-08
#> 5  11.6670457   0.5754740 20.2738033 1.039110e-06
#> 14 11.7990198   0.5243007 22.5042969 9.744242e-09
#> 7  19.6420227   1.6727894 11.7420778 2.991518e-07
#> 24 -0.8836338   0.9024171 -0.9791856 2.002545e-01
#> 30  8.5991278   0.9874876  8.7080871 3.561671e-06
#> 22 19.6241673   6.5439869  2.9988091 1.187269e-02
#> 23 10.0733479   1.1701968  8.6082510 2.705767e-07
#> 27 18.6835470   3.8095714  4.9043698 6.998944e-05
#> 28 12.5784247   0.9063180 13.8785991 2.470118e-07
#> 26 23.7244981   4.1020833  5.7835242 2.979918e-04
#> 25 16.8322652   1.5435996 10.9045538 2.205875e-07
#> 29         NA          NA         NA           NA
#> 21 10.8284066   0.2755632 39.2955447 2.675093e-10
#> 33  5.7518758   0.3805241 15.1156692 5.351638e-07
#> 37 26.8210511   9.3167434  2.8788011 3.615717e-03
#> 36 11.1189397   0.6128949 18.1416745 1.615805e-06
#> 31 53.3272678  42.1136935  1.2662691 3.322759e-02
#> 39 59.7511517  67.1490520  0.8898287 2.897457e-01
#> 38 35.4032577   9.1341217  3.8759345 4.027337e-03
#> 32 17.7623107   1.4011806 12.6766749 1.247709e-07
#> 40 22.9206132   2.5656644  8.9335976 2.048940e-07
#> 34 19.8529590   1.5243475 13.0239064 5.773048e-06
#> 35 13.8114475   0.4680940 29.5057127 8.795260e-11
#> 44  5.1607388   0.5398253  9.5600171 2.248875e-07
#> 45 11.7500191   1.3370619  8.7879397 1.204376e-04
#> 43  5.9690157   0.2228179 26.7887591 4.838505e-09
#> 41  8.8259833   0.5915923 14.9190293 1.653646e-07
#> 47 10.1377418   0.7870589 12.8805373 6.324874e-07
#> 49 12.3736064   1.0005770 12.3664712 4.730394e-07
#> 46 15.6716692   1.7149658  9.1381820 9.657759e-06
#> 50 15.2470497   1.2401122 12.2948956 5.166380e-08
#> 42 16.7920728   1.4628604 11.4789305 3.504580e-07
#> 48 22.8432312   2.4664720  9.2615003 7.636661e-06
#>    scal 
#>     Estimate Std. Error    t value     Pr(>|t|)
#> 18        NA         NA         NA           NA
#> 16  3.416060  3.6915024  0.9253848 1.470455e-01
#> 15  3.111387  1.3421561  2.3182007 1.213418e-03
#> 13 28.264875 27.3322912  1.0341202 2.714148e-01
#> 9   4.203362  0.7177723  5.8561224 8.833591e-05
#> 20 13.722943  4.1355081  3.3183210 2.915377e-06
#> 10  9.065047  1.7595462  5.1519231 1.356585e-06
#> 8   5.955824  0.8176362  7.2841980 2.869298e-05
#> 17 10.084052  1.9257746  5.2363616 2.654356e-05
#> 19        NA         NA         NA           NA
#> 4   9.396451  1.3134711  7.1539075 1.623953e-04
#> 6   4.230045  0.3369910 12.5523961 3.160838e-06
#> 11  3.703533  0.2429933 15.2412975 1.218072e-05
#> 3   7.333444  0.6460551 11.3511114 1.261508e-06
#> 1  11.405209  1.5110467  7.5478862 5.079519e-07
#> 12  7.924723  0.7430491 10.6651408 2.247199e-05
#> 2   8.604490  0.8315294 10.3477889 1.187411e-09
#> 5   5.238298  0.3284519 15.9484461 7.709893e-06
#> 14  5.429882  0.2917093 18.6140199 5.156180e-08
#> 7   7.206433  0.4399517 16.3800546 1.646215e-08
#> 24  2.992640  0.9764623  3.0647773 1.916036e-03
#> 30  6.520010  0.7042831  9.2576553 2.135171e-06
#> 22 11.309157  1.8935278  5.9725332 1.479499e-04
#> 23  6.984844  0.7083695  9.8604519 8.342604e-08
#> 27  9.397528  1.1222445  8.3738686 8.909070e-07
#> 28  6.327577  0.4470537 14.1539535 2.084802e-07
#> 26  9.229076  0.8365659 11.0320966 1.801710e-06
#> 25  7.425346  0.5140366 14.4451713 1.901046e-08
#> 29        NA         NA         NA           NA
#> 21  4.517433  0.1794793 25.1696604 1.389968e-08
#> 33  4.385078  0.3806941 11.5186415 5.276499e-06
#> 37 11.303631  1.7236854  6.5578270 9.478689e-06
#> 36  5.624619  0.3581607 15.7041749 5.390044e-06
#> 31 11.496481  1.2689694  9.0596991 2.329657e-08
#> 39 11.375044  1.1774017  9.6611406 6.626744e-07
#> 38  9.733405  0.8055322 12.0831979 8.034585e-07
#> 32  7.224076  0.4320686 16.7197450 1.106794e-08
#> 40  8.172846  0.5294484 15.4365316 1.684528e-09
#> 34  7.247460  0.3939947 18.3948167 3.126884e-07
#> 35  5.445281  0.2169147 25.1033343 3.719624e-10
#> 44  4.570223  0.5111298  8.9414130 3.563475e-07
#> 45  7.325136  0.6860879 10.6766741 2.663674e-05
#> 43  3.553620  0.2113603 16.8130878 2.843615e-07
#> 41  5.707897  0.4272780 13.3587437 4.284616e-07
#> 47  6.269003  0.4874382 12.8611236 6.406856e-07
#> 49  6.798584  0.4936114 13.7731497 1.871137e-07
#> 46  7.788419  0.6268122 12.4254427 7.409088e-07
#> 50  7.135461  0.4728886 15.0890964 8.554035e-09
#> 42  7.393208  0.4888386 15.1240254 3.194392e-08
#> 48  8.029813  0.5089288 15.7778699 8.282039e-08
#> 
#> Residual standard error: 4.871706 on 411 degrees of freedom

这里的 SSlogis() 是 R 内置的逻辑斯蒂自启动函数,能帮我们自动选初值。

2.2.2.5nlme() 构建混合效应模型

# 混合效应非线性模型:固定效应 + 随机效应
fm2 <- nlme(
  weight ~ SSlogis(Time, Asym, xmid, scal),
  data = ChickWeight,
  fixed = Asym + xmid + scal ~ Diet,
  random = Asym ~ 1 | Chick
)
summary(fm2)
#> Nonlinear mixed-effects model fit by maximum likelihood
#>   Model: weight ~ SSlogis(Time, Asym, xmid, scal) 
#>   Data: ChickWeight 
#>        AIC      BIC    logLik
#>   4979.698 5001.496 -2484.849
#> 
#> Random effects:
#>  Formula: Asym ~ 1 | Chick
#>             Asym Residual
#> StdDev: 79.43488 14.90059
#> 
#> Fixed effects:  list(Asym ~ 1, xmid ~ 1, scal ~ 1) 
#>          Value Std.Error  DF  t-value p-value
#> Asym 297.94547 15.627598 526 19.06534       0
#> xmid  14.20968  0.583618 526 24.34755       0
#> scal   7.11553  0.242564 526 29.33461       0
#>  Correlation: 
#>      Asym  xmid 
#> xmid 0.683      
#> scal 0.639 0.934
#> 
#> Standardized Within-Group Residuals:
#>         Min          Q1         Med          Q3         Max 
#> -3.00857103 -0.56383025  0.03704846  0.72162682  3.18303073 
#> 
#> Number of Observations: 578
#> Number of Groups: 50
  • 固定效应:检查饮食对参数(最大体重、拐点、陡峭度)的影响
  • 随机效应:允许不同小鸡的最大体重不同

2.2.2.6 画拟合效果

nlme模型的拟合结果与原始数据一起可视化,是评估模型效果和进行诊断的重要步骤。nlme软件包自带的 plot() 函数能快速生成图形,但如果你想获得更美观、更灵活的定制化图表,那么使用 ggplot2 是更好的选择。

下面我将介绍使用 ggplot2 可视化 nlme 模型拟合效果的方法。这种方法的核心思想是利用 predict() 函数从 nlme 模型中提取拟合的预测值,然后将这些预测值添加到原始数据集中,最后用 ggplot2 绘制。

predict() 函数对于 nlme 对象非常有用,你可以指定预测的层级:

  • level = 0:预测固定效应的拟合值(即群体平均生长曲线)。
  • level = 1:预测随机效应固定效应的拟合值(即每只小鸡的个体生长曲线)。

首先,我们将 fm2 模型的两种拟合值都提取出来,并添加到 ChickWeight 数据框中。

# 提取群体平均预测值(固定效应)
ChickWeight$fixed_pred <- predict(fm2, level = 0)

# 提取个体预测值(固定效应 + 随机效应)
ChickWeight$indiv_pred <- predict(fm2, level = 1)

我们先绘制各饲料组的平均生长曲线。这能帮助我们评估模型对群体趋势的拟合效果。

library(ggplot2)

ggplot(ChickWeight, aes(x = Time, y = weight, color = Diet)) +
  # 绘制原始数据点
  geom_point(alpha = 0.5) +
  # 绘制群体平均拟合曲线
  geom_line(aes(y = fixed_pred), linewidth = 1.2) +
  # 按饲料分面
  facet_wrap(~Diet) +
  labs(
    title = "模型对群体平均生长曲线的拟合",
    x = "日龄",
    y = "体重(g)"
  ) +
  theme_bw()
图 2.3: nlme 模型对群体平均生长曲线的拟合

从这个图中,你可以清楚地看到模型拟合的逻辑斯蒂曲线(粗线)与实际数据点(半透明点)的匹配程度。

接下来,我们绘制每只小鸡的个体拟合曲线,这能直观地显示模型对随机效应的捕捉能力。

ggplot(ChickWeight, aes(x = Time, y = weight, group = Chick, color = Diet)) +
  # 绘制原始数据点和个体曲线
  geom_line(aes(y = weight), alpha = 0.4) +
  geom_point(alpha = 0.5) +
  # 绘制个体拟合曲线
  geom_line(aes(y = indiv_pred), linewidth = 1) +
  # 按饲料分面
  facet_wrap(~Diet) +
  labs(
    title = "模型对个体生长曲线的拟合",
    x = "日龄",
    y = "体重(g)"
  ) +
  theme_bw()
图 2.4: nlme 模型对个体生长曲线的拟合

在这个图中,你可以看到每只小鸡各自的真实生长轨迹(细线)和模型为它们拟合的独特曲线(粗线)。这展示了模型如何利用随机效应来解释个体差异。

综上所述,这个例子展示了如何用 非线性混合效应模型真实拟合 ChickWeight 数据。

2.2.3 PlantGrowth:单因素 ANOVA、组间均值比较与 Tukey 检验

PlantGrowth 数据集记录了两种处理(trt1, trt2)和一个对照组(ctrl)对植物干重的影响。研究采用的实验设计是一个典型的完全随机设计,共有三组,每组包含 10 株植物。这里我们将使用该数据集演示如何使用单因素方差分析(One-way ANOVA)来检验三个或更多组的均值是否存在显著差异,并使用事后多重比较(如 Tukey HSD)来确定具体是哪几组之间存在差异,最终绘制出发表级的图片。

2.2.3.1 快速认识与可视化

与前面两个例子类似,在进行统计分析之前,首先通过数据概要和可视化来快速了解数据的分布和特征,这是 R 数据分析的良好实践。

# 载入数据集
data(PlantGrowth)

# 查看数据结构
str(PlantGrowth)
#> 'data.frame':    30 obs. of  2 variables:
#>  $ weight: num  4.17 5.58 5.18 6.11 4.5 4.61 5.17 4.53 5.33 5.14 ...
#>  $ group : Factor w/ 3 levels "ctrl","trt1",..: 1 1 1 1 1 1 1 1 1 1 ...
# 查看数据概要
summary(PlantGrowth)
#>      weight       group   
#>  Min.   :3.590   ctrl:10  
#>  1st Qu.:4.550   trt1:10  
#>  Median :5.155   trt2:10  
#>  Mean   :5.073            
#>  3rd Qu.:5.530            
#>  Max.   :6.310

使用 ggplot2 绘制箱线图和数据点,可以直观比较各组数据的分布情况。

ggplot(PlantGrowth, aes(x = group, y = weight, color = group)) +
  # 添加箱线图,展示中位数、四分位数和异常值
  geom_boxplot(alpha = 0.7) +
  # 添加原始数据点,jitter 避免重叠
  geom_jitter(width = 0.1, alpha = 0.8) +
  theme_bw() +
  labs(title = "PlantGrowth: 不同组别植物干重分布",
       x = "组别",
       y = "植物干重 (g)") +
  # 移除图例
  guides(color = "none")
图 2.5: PlantGrowth 不同组别植物干重分布

从箱线图中可以初步观察到:trt1 组的植物干重中位数最低,而 trt2 组的中位数最高。这暗示了三种处理可能对植物生长有不同影响,但要确认这种差异是否具有统计学意义,我们需要进行方差分析。

2.2.3.2 单因素方差分析(ANOVA)

ANOVA 的核心思想是分解总变异。它将植物干重总体的变异分解为组间变异(由不同实验组引起的)和组内变异(各组内部的随机差异)。通过比较这两部分变异的相对大小(即 F 值),我们可以判断组均值是否真的存在显著差异。

# 构建单因素方差分析模型
fit_pg <- aov(weight ~ group, data = PlantGrowth)

# 输出 ANOVA 结果摘要
summary(fit_pg)
#>             Df Sum Sq Mean Sq F value Pr(>F)  
#> group        2  3.766  1.8832   4.846 0.0159 *
#> Residuals   27 10.492  0.3886                 
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

summary(fit_pg) 的输出显示了方差分析表。 group 行的 Pr(>F) 值(p 值)为 0.0159,它小于 0.05。这表明至少有一组的均值与其他组存在显著差异。然而,ANOVA 结果本身并不能告诉我们具体是哪两组之间有差异。

2.2.3.3 模型诊断

在依赖 ANOVA 结果之前,我们需要检查其两个基本前提假设:残差的正态性方差齐性(各组方差相等)。

1. 方差齐性检验(Levene’s test)

我们使用 car 包中的 leveneTest() 函数。

  • 零假设 (\(H_0\)):各组的方差相等。
  • 备择假设 (\(H_a\)):至少有一组的方差与其他组不等。
library(car)
leveneTest(weight ~ group, data = PlantGrowth)              
#> Levene's Test for Homogeneity of Variance (center = median)
#>       Df F value Pr(>F)
#> group  2  1.1192 0.3412
#>       27

结果中,Pr(>F) 值为 0.5097,它远大于 0.05。这意味着我们没有足够的证据拒绝零假设,可以认为各组的方差是齐性的,满足 ANOVA 的前提。

2. 残差正态性检验(Shapiro-Wilk test)

我们使用 shapiro.test() 函数来检验模型的残差是否服从正态分布。

  • 零假设 (\(H_0\)):残差服从正态分布。
  • 备择假设 (\(H_a\)):残差不服从正态分布。
shapiro.test(residuals(fit_pg))
#> 
#>  Shapiro-Wilk normality test
#> 
#> data:  residuals(fit_pg)
#> W = 0.96607, p-value = 0.4379

结果中,p-value0.4497,它远大于 0.05。因此我们没有足够的证据拒绝零假设,可以认为残差服从正态分布,满足 ANOVA 的另一个前提。

2.2.3.4 组间多重比较(Tukey HSD)

既然 ANOVA 结果是显著的,且前提假设均满足,我们可以进行事后检验(Post-hoc Test)来确定具体是哪几组之间有显著差异。Tukey HSD(Honestly Significant Difference)是一种常用的方法,它能在所有可能的两两比较中控制第一类错误(即错误地拒绝零假设的概率)。

# 使用 TukeyHSD() 函数进行事后多重比较
TukeyHSD(fit_pg)
#>   Tukey multiple comparisons of means
#>     95% family-wise confidence level
#> 
#> Fit: aov(formula = weight ~ group, data = PlantGrowth)
#> 
#> $group
#>             diff        lwr       upr     p adj
#> trt1-ctrl -0.371 -1.0622161 0.3202161 0.3908711
#> trt2-ctrl  0.494 -0.1972161 1.1852161 0.1979960
#> trt2-trt1  0.865  0.1737839 1.5562161 0.0120064

Tukey HSD 的输出表格展示了每对组之间的均值差异、95%置信区间,以及调整后的 p 值(p adj)。

  • trt1-ctrl:p 值大于 0.05,表明 trt1 组与对照组之间没有显著差异
  • trt2-ctrl:p 值大于 0.05,表明 trt2 组与对照组之间没有显著差异
  • trt2-trt1:p 值小于 0.05,表明 trt2 组与 trt1 组之间存在显著差异

2.2.3.5 输出发表级图片

最后,我们使用 ggpubr 软件包为箱线图添加显著性标记。

library(ggpubr)

ggplot(PlantGrowth, aes(x = group, y = weight, color = group)) +
  geom_boxplot() +
  geom_jitter(width = 0.1) +
  stat_compare_means(
    method = "wilcox.test",
    label = "p.signif",
    comparisons = list(c("trt2","trt1"))) +
  labs(title = "PlantGrowth: 不同组别植物干重分布",
       x = "组别",
       y = "植物干重 (g)") +
  theme_bw() +
  guides(color = "none")
图 2.6: PlantGrowth 组间比较(带显著性标记)
Note

这里使用 Wilcoxon 秩和检验标注显著性。在样本量较小(每组仅 10 个观测)的情况下,非参数方法更为稳健。前面的 Tukey HSD 结果已经表明 trt2trt1 之间存在显著差异,此处的可视化结果与之一致。

通过对 PlantGrowth 数据集的统计分析,我们得出以下结论:

  1. 单因素方差分析(ANOVA)结果显示,group 因子对植物干重有显著影响 (\(p < 0.05\))。
  2. 模型诊断表明,各组方差齐性良好,且残差服从正态分布,满足 ANOVA 的前提假设。
  3. Tukey HSD 事后多重比较进一步揭示了差异的具体来源:trt2 处理组的植物干重显著高于 trt1 处理组。而 trt1trt2 与对照组相比,均无显著差异。

最终,我们可以得出结论:在这项研究中,trt2 这种处理方式能够显著提高植物干重,而 trt1 的效果与对照组基本相同。