library(tidyverse)7 R 数据处理进阶
8 R 数据处理进阶
上一章我们学习了 R 的基础操作。本章将介绍 tidyverse——R 中最流行的数据处理生态系统,它能让你的数据处理代码更简洁、更易读。
8.1 tidyverse 简介
tidyverse 是一组协同工作的 R 包集合,由 Hadley Wickham 等人开发:
加载 tidyverse 会同时加载以下核心包:
| 包名 | 功能 |
|---|---|
| dplyr | 数据操作(筛选、排序、汇总) |
| tidyr | 数据重塑(长宽格式转换) |
| ggplot2 | 数据可视化 |
| readr | 数据读取 |
| stringr | 字符串处理 |
| forcats | 因子处理 |
| tibble | 增强版数据框 |
| purrr | 函数式编程 |
8.2 管道操作符
管道操作符 |> 是 tidyverse 编程风格的核心。它的作用是:把左边的结果传给右边函数的第一个参数。这意味着右边的函数必须在第一个参数位置接收数据,tidyverse 的函数都遵循这个设计。
# 传统写法(从内向外读,难以理解)
round(mean(c(1.5, 2.3, 3.7, 4.1)), 1)
# 管道写法(从左到右读,清晰直观)
c(1.5, 2.3, 3.7, 4.1) |>
mean() |>
round(1)上面的管道写法等价于:
c(1.5, 2.3, 3.7, 4.1)的结果传给mean()→ 得到2.92.9传给round(1)→ 即round(2.9, 1)→ 得到2.9
你可以把 |> 读作”然后”:取这些数字,然后求平均,然后四舍五入。
在 RStudio 中,按 Ctrl + Shift + M 可以快速输入管道操作符 %>%(如果你使用的是 magrittr/tidyverse 包)或 Ctrl + Shift + M 输入原生管道 |>(R 4.1+)。此外,还有一些提高效率的快捷键值得掌握。
常用 RStudio 快捷键:
Ctrl + Shift + M — 输入管道操作符 |>(Windows/Linux)或 %>%(macOS)。这是使用管道时最常用的快捷键,省去每次手动输入符号的麻烦。
Ctrl + Shift + K — 渲染当前 Quarto/R Markdown 文档。一键生成 PDF、HTML 或 Word 格式的输出报告。
Ctrl + Shift + Enter — 运行当前代码块(而非只运行当前行)。在调试脚本时非常有用。
Ctrl + Shift + C — 批量注释/取消注释选中的多行代码。
Ctrl + Alt + I — 在 R Markdown/Quarto 中插入新的代码块。这是写报告时的常用操作。
Ctrl + Shift + A — 重新格式化(reformat)选中的 R 代码,自动对齐缩进和赋值运算符。
管道与快捷键的配合使用:
管道操作通常与快捷键配合使用可以大幅提升效率。以下是一个典型的工作流:
library(tidyverse)
# 演示数据:土壤调查数据
soil <- tibble(
site_id = rep(c("S1", "S2", "S3", "S4"), each = 3),
depth = rep(c("0-10cm", "10-20cm", "20-40cm"), 4),
ph = round(rnorm(12, mean = 5.5, sd = 0.5), 2),
organic_c = round(rnorm(12, mean = 25, sd = 8), 2)
)
# 不用管道(逐行赋值)
result_1 <- filter(soil, ph > 5.0)
result_2 <- mutate(result_1, log_oc = log(organic_c))
result_3 <- arrange(result_2, desc(log_oc))
# 用管道(优雅简洁)
result <- soil |>
filter(ph > 5.0) |>
mutate(log_oc = log(organic_c)) |>
arrange(desc(log_oc))
# 快捷键 Ctrl+Shift+M 输入 |> 后,再用 Ctrl+Shift+Enter 运行整块
# 进阶:多步汇总
summary_table <- soil |>
group_by(site_id) |>
summarise(
mean_ph = mean(ph),
mean_oc = mean(organic_c),
n_samples = n()
) |>
mutate(status = ifelse(mean_ph > 5.5, "偏酸", "中性"))
print(summary_table)生态学案例:
在分析马尾松林土壤数据时,一位学生写了20行嵌套函数:arrange(mutate(filter(group_by(survey, site), ...)))。这种写法不仅难以阅读,而且调试困难。改用管道后,同样的操作变成了4行线性代码,每一步的功能一目了然。更重要的是,当他需要在中途增加一个步骤(如计算多样性指数)时,只需要在管道中插入一行即可,无需重写整个嵌套结构。管道不仅是语法糖,更是提升代码可维护性的关键工具。
扩展记录: 2026-04-10 | 扩展者:Clawd | 目标字数:800+
|> 与 %>% 的区别
你可能在网上看到另一种管道 %>%(来自 magrittr 包)。主要区别:
|>是 R 4.1+ 内置的原生管道,不需要加载任何包%>%是 magrittr 包提供的管道,需要加载 tidyverse 或 magrittr%>%支持用.作为占位符(如x %>% f(2, .)),|>不支持这种写法
本课程统一使用 |>。在大多数场景下两者可以互换,但如果你在旧教程中看到 %>% 配合 . 占位符的用法,需要改写为中间变量或匿名函数的形式。
8.2.1 示例数据
本章使用一个模拟的植物样方调查数据集:
# 创建示例数据
set.seed(2027)
survey <- tibble(
plot_id = rep(paste0("P", 1:10), each = 5),
species = sample(c("马尾松", "杉木", "桉树", "樟树", "楠木"), 50, replace = TRUE),
height = round(rnorm(50, mean = 12, sd = 4), 1),
dbh = round(rnorm(50, mean = 20, sd = 8), 1),
habitat = rep(sample(c("山脊", "山坡", "山谷"), 10, replace = TRUE), each = 5),
soil_ph = round(rnorm(50, mean = 5.5, sd = 0.8), 2)
)
# 人为加入一些缺失值
survey$height[c(3, 17, 28)] <- NA
survey$dbh[c(8, 42)] <- NA
head(survey, 10)8.3 dplyr:数据操作五大动词
8.3.1 filter() — 筛选行
# 筛选马尾松
survey |>
filter(species == "马尾松")
# 多条件筛选:株高 > 15 且 土壤 pH < 6
survey |>
filter(height > 15, soil_ph < 6)
# 或条件:马尾松或杉木
survey |>
filter(species %in% c("马尾松", "杉木"))8.3.2 select() — 选择列
# 选择特定列
survey |>
select(plot_id, species, height)
# 排除某列
survey |>
select(-soil_ph)
# 选择数值列
survey |>
select(where(is.numeric))8.3.3 mutate() — 新增/修改列
# 新增列:计算树木断面积(basal area)
survey |>
mutate(
basal_area = pi * (dbh / 200)^2, # 单位:m²
height_class = case_when(
is.na(height) ~ NA_character_, # 先处理缺失值,避免被误判
height < 8 ~ "矮",
height < 15 ~ "中",
TRUE ~ "高"
)
) |>
head()8.3.4 arrange() — 排序
# 按株高降序排列
survey |>
arrange(desc(height)) |>
head()
# 先按样方排序,再按株高排序
survey |>
arrange(plot_id, desc(height)) |>
head()8.3.5 summarise() + group_by() — 分组汇总
# 按物种分组统计
survey |>
group_by(species) |>
summarise(
n = n(),
mean_height = mean(height, na.rm = TRUE),
sd_height = sd(height, na.rm = TRUE),
mean_dbh = mean(dbh, na.rm = TRUE)
) |>
arrange(desc(mean_height))# 按样方和生境分组
survey |>
group_by(habitat) |>
summarise(
n_trees = n(),
n_species = n_distinct(species),
mean_ph = mean(soil_ph, na.rm = TRUE)
)8.4 数据查看与质量检查
在实际数据分析前,首先要对数据进行全面”体检”,了解数据的基本情况,及时发现缺失值、异常值、类型错误等问题。
8.4.1 基本查看函数
# 查看数据基本信息(显示每列的类型和前几个值)
glimpse(survey)
# 查看数据前几行
head(survey)
# 查看数据后几行
tail(survey)
# 数据框的行列数
dim(survey)
nrow(survey) # 行数
ncol(survey) # 列数
# 查看完整列名
names(survey)
# 查看数值列的统计摘要
summary(survey)8.4.2 缺失值诊断
# 统计每列缺失值数量
survey |>
summarise(across(everything(), \(x) sum(is.na(x)))) |>
pivot_longer(everything(), names_to = "列名", values_to = "缺失数")
# 查看含缺失值的行
survey |>
filter(if_any(everything(), is.na)) |>
head()
# 缺失值热图(可视化缺失模式)
# install.packages("naniar")
library(naniar)
gg_miss_var(survey)8.4.3 异常值检测
# 使用箱线图识别异常值
survey |>
filter(!is.na(height)) |>
ggplot(aes(y = height)) +
geom_boxplot()
# 基于 1.5 倍 IQR 法则标记异常值
survey |>
mutate(
height_q1 = quantile(height, 0.25),
height_q3 = quantile(height, 0.75),
height_iqr = height_q3 - height_q1,
is_outlier = height < (height_q1 - 1.5 * height_iqr) |
height > (height_q3 + 1.5 * height_iqr)
) |>
filter(is_outlier) |>
select(plot_id, species, height)- 运行
glimpse()—— 快速了解列名和类型 - 运行
summary()—— 查看数值列的分布(最小、均值、最大) - 检查
is.na()—— 确认缺失值的位置和数量 - 可视化 —— 用
ggplot2绑制直方图/箱线图发现异常值
8.4.4 across():对多列同时操作
across() 是 dplyr 1.0+ 引入的强大函数,可以在 mutate() 和 summarise() 中对多列同时应用相同的变换:
# 对所有数值列计算均值
survey |>
summarise(across(where(is.numeric), mean, na.rm = TRUE))
# 对多个特定列分别计算
survey |>
summarise(across(c(height, dbh, soil_ph), list(mean = mean, sd = sd), na.rm = TRUE))
# 在 mutate 中使用 across 批量标准化
survey |>
mutate(across(where(is.numeric), \(x) scale(x)[,1], .names = "z_{.col}")) |>
select(plot_id, z_height, z_dbh, z_soil_ph) |>
head()
# 按组计算并保留分组列
survey |>
group_by(habitat) |>
summarise(across(where(is.numeric), \(x) round(mean(x, na.rm = TRUE), 2)), .groups = "drop")生态学应用——多样本多指标汇总:
# 快速生成所有数值指标的汇总表
survey |>
group_by(species) |>
summarise(
across(where(is.numeric), \(x) list(
mean = round(mean(x, na.rm = TRUE), 2),
sd = round(sd(x, na.rm = TRUE), 2),
min = round(min(x, na.rm = TRUE), 2),
max = round(max(x, na.rm = TRUE), 2)
))
)8.4.5 if_else() 与 case_when() 的区别
| 函数 | 适用场景 | 返回类型 |
|---|---|---|
if_else(condition, true, false) |
单一条件判断 | 必须类型一致 |
case_when(...) |
多条件多返回值 | 灵活,不要求类型一致 |
dplyr::if_else(...) |
同上,但参数更严格 | 严格类型检查 |
# if_else:简单二选一
survey |>
mutate(height_level = if_else(height > 12, "高", "矮"))
# case_when:多条件(类似其他语言的 switch)
survey |>
mutate(dbh_class = case_when(
dbh < 10 ~ "小径材",
dbh >= 10 & dbh < 25 ~ "中径材",
dbh >= 25 ~ "大径材",
TRUE ~ NA_character_
))8.4.6 实战:条件筛选与分组的典型模式
# 模式 1:找出每个样方中最高的树
tallest_per_plot <- survey |>
filter(!is.na(height)) |>
slice_max(height, n = 1, by = plot_id)
# 模式 2:计算每个物种的超优势度(占样方的比例)
dominance <- survey |>
filter(!is.na(height)) |>
group_by(plot_id, species) |>
summarise(n_trees = n(), .groups = "drop_last") |>
mutate(prop = n_trees / sum(n_trees)) |>
slice_max(prop, n = 1, by = plot_id)
# 模式 3:按条件筛选后分组统计
survey |>
filter(
!is.na(height),
!is.na(dbh),
habitat %in% c("山脊", "山坡")
) |>
group_by(habitat, species) |>
summarise(
n = n(),
mean_h = round(mean(height), 1),
mean_dbh = round(mean(dbh), 1),
.groups = "drop"
) |>
filter(n >= 3) |>
arrange(habitat, desc(mean_h))8.5 组合使用:数据处理流水线
dplyr 的强大之处在于可以用管道将多个操作串联起来:
# 完整的数据处理流水线
result <- survey |>
filter(!is.na(height), !is.na(dbh)) |> # 1. 去除缺失值
mutate(basal_area = pi * (dbh / 200)^2) |> # 2. 计算断面积
group_by(habitat, species) |> # 3. 按生境和物种分组
summarise(
n = n(),
mean_height = round(mean(height), 1),
total_ba = round(sum(basal_area), 4),
.groups = "drop"
) |>
arrange(habitat, desc(total_ba)) # 4. 排序
result8.6 tidyr:数据重塑
生态学数据经常需要在”长格式”和”宽格式”之间转换。
8.6.1 宽格式 → 长格式:pivot_longer()
# 宽格式数据:每个物种一列
wide_data <- tibble(
plot = paste0("P", 1:5),
马尾松 = c(12, 8, 0, 15, 6),
杉木 = c(5, 0, 10, 3, 8),
桉树 = c(0, 7, 4, 0, 2)
)
wide_data
# 转为长格式
long_data <- wide_data |>
pivot_longer(
cols = -plot, # 除了 plot 列,其他都转
names_to = "species", # 列名变成 species 列
values_to = "count" # 值变成 count 列
)
long_data8.6.2 长格式 → 宽格式:pivot_wider()
pivot_wider() 是 pivot_longer() 的逆操作,用于将长格式数据转换为宽格式。在生态学数据分析中,这种转换常用于以下场景:
典型应用场景:
- 物种多度矩阵构建:将样方-物种-多度的长格式数据转换为”样方×物种”矩阵,用于排序分析(PCA、NMDS)或多样性指数计算。
- 时间序列数据展示:将监测站点-日期-指标的长格式数据转换为”日期×站点”表格,便于横向对比不同站点的变化趋势。
- 实验数据汇总:将处理-重复-测量值的长格式数据转换为”处理×重复”表格,用于方差分析或制作汇总表。
基本语法:
# 转回宽格式
long_data |>
pivot_wider(
names_from = species, # 哪一列的值变成新列名
values_from = count # 哪一列的值填充到新列中
)参数说明:
names_from:指定哪一列的唯一值将成为新列的列名(如物种名)values_from:指定哪一列的值将填充到新列中(如多度值)values_fill:当某些组合缺失时,用什么值填充(默认NA,常用0)names_prefix:为新列名添加前缀(如"sp_")
生态学实战案例——物种多度矩阵构建:
# 模拟样方调查长格式数据
set.seed(2027)
survey_long <- tibble(
plot_id = rep(paste0("P", 1:5), each = 3),
species = rep(c("马尾松", "杉木", "桉树"), 5),
abundance = sample(0:20, 15, replace = TRUE)
) |>
filter(abundance > 0) # 移除未出现的物种
# 转换为物种×样方矩阵(用于 vegan 包的排序分析)
species_matrix <- survey_long |>
pivot_wider(
names_from = species,
values_from = abundance,
values_fill = 0 # 未记录的物种填充为 0
)
species_matrix处理重复值问题:
当 names_from 和 id_cols(标识列)的组合不唯一时,pivot_wider() 会报错或产生列表列。此时需要先聚合数据:
# 错误示例:同一样方同一物种有多条记录
duplicate_data <- tibble(
plot = c("P1", "P1", "P2"),
species = c("马尾松", "马尾松", "杉木"),
count = c(5, 8, 10)
)
# 解决方案:先聚合再转换
duplicate_data |>
group_by(plot, species) |>
summarise(total_count = sum(count), .groups = "drop") |>
pivot_wider(names_from = species, values_from = total_count, values_fill = 0)高级用法——多值列转换:
# 同时转换多个测量值(如均值和标准差)
survey_stats <- tibble(
plot = rep(paste0("P", 1:3), each = 2),
species = rep(c("马尾松", "杉木"), 3),
mean_height = round(rnorm(6, 12, 2), 1),
sd_height = round(rnorm(6, 1.5, 0.3), 2)
)
survey_stats |>
pivot_wider(
names_from = species,
values_from = c(mean_height, sd_height),
names_glue = "{species}_{.value}" # 自定义列名格式
)注意事项:
- 转换前确保
names_from列的值适合作为列名(避免特殊字符或过长) - 如果原数据中某些组合缺失,
values_fill = 0可避免NA干扰后续计算 - 宽格式数据占用内存较大,仅在必要时转换(如输出报告或特定分析需求)
扩展记录: 2026-04-11 | 扩展者:Clawd | 目标字数:800+
数据格式的选择直接影响分析效率和代码复杂度。理解长格式和宽格式的适用场景,可以避免在数据处理中走弯路。
长格式(Long Format)的特点:
- 每一行代表一个观测值(observation)
- 变量名存储在一列中(如”物种”列),对应的值存储在另一列中(如”多度”列)
- 数据行数多,列数少
- 符合”整洁数据”(Tidy Data)原则:每个变量一列,每个观测一行
宽格式(Wide Format)的特点:
- 每一行代表一个观测单元(如一个样方)
- 不同变量分散在多列中(如每个物种一列)
- 数据行数少,列数多
- 更接近人类阅读习惯(如 Excel 表格)
格式选择的决策树:
需要用 ggplot2 绘图?
└─ 是 → 长格式(facet_wrap/facet_grid 需要长格式)
└─ 否 → 继续判断
需要按组统计(group_by + summarise)?
└─ 是 → 长格式(分组变量在一列中更方便)
└─ 否 → 继续判断
需要进行矩阵运算(如 PCA、相关矩阵)?
└─ 是 → 宽格式(样本×变量矩阵)
└─ 否 → 继续判断
需要展示给他人查看或导出到 Excel?
└─ 是 → 宽格式(更易读)
└─ 否 → 默认使用长格式(便于后续处理)
生态学典型场景对比:
| 分析任务 | 推荐格式 | 原因 |
|---|---|---|
| 物种多样性指数计算(Shannon、Simpson) | 长格式 | 按样方分组计算,每个物种一行更方便 |
| ggplot2 绘图(如物种多度柱状图) | 长格式 | facet_wrap(~species) 需要物种在一列中 |
| 排序分析(NMDS、PCA) | 宽格式 | vegan 包要求样方×物种矩阵 |
| 相关性分析(cor()) | 宽格式 | 需要变量在不同列中 |
| 数据展示与报告 | 宽格式 | 表格更易读,符合人类阅读习惯 |
实战案例——物种多度数据的格式转换:
# 宽格式(物种×样方表,适合展示)
sp_wide <- tibble(
plot = paste0("P", 1:6),
马尾松 = c(12, 8, 15, 0, 10, 6),
杉木 = c(5, 0, 8, 12, 3, 9),
阔叶树 = c(8, 15, 6, 5, 12, 10)
)
# 宽 → 长:用于 ggplot2 绘图和多样性计算
sp_long <- sp_wide |>
pivot_longer(-plot, names_to = "species", values_to = "abundance") |>
filter(abundance > 0) # 移除未出现的物种
# 长 → 宽:用于 vegan 包的排序分析
sp_wide2 <- sp_long |>
pivot_wider(names_from = species, values_from = abundance, values_fill = 0)一般原则:
- 存储和分析用长格式:便于筛选、分组、可视化
- 展示和报告用宽格式:更符合人类阅读习惯
- 矩阵运算用宽格式:PCA、相关分析等需要样本×变量矩阵
- 不确定时优先长格式:长格式更灵活,可以随时转换为宽格式
常见误区:
- ❌ 认为宽格式”更整洁”(实际上长格式才是 Tidy Data)
- ❌ 在 Excel 中习惯宽格式,就在 R 中也一直用宽格式(会增加代码复杂度)
- ❌ 不知道可以随时转换,导致为了适应数据格式而修改分析流程
扩展记录: 2026-04-11 | 扩展者:Clawd | 目标字数:800+
植物群落调查数据通常记录为”物种×样方”矩阵(宽格式),但进行以下分析时需要转换为长格式:
| 分析目的 | 推荐格式 | 原因 |
|---|---|---|
| 物种多样性指数计算(Shannon、Simpson) | 长格式 | 按样方分组计算,每个物种一行更方便 |
| ggplot2 绑图 | 长格式 | facet_wrap 按物种分面需要长格式 |
| DCA/CCA 排序分析(vegan 包) | 长格式 | 多数排序函数要求长格式 |
| PCA/NMDS 分析 | 宽格式 | 物种×样方矩阵直接输入 |
| 数据展示与报告 | 宽格式 | 表格更易读 |
示例:物种多度数据的宽转长与长转宽
# 宽格式(物种×样方表)
sp_wide <- tibble(
plot = paste0("P", 1:6),
马尾松 = c(12, 8, 15, 0, 10, 6),
杉木 = c(5, 0, 8, 12, 3, 9),
阔叶树 = c(8, 15, 6, 5, 12, 10)
)
# 宽 → 长:用于多样性计算
sp_long <- sp_wide |>
pivot_longer(-plot, names_to = "species", values_to = "abundance") |>
filter(abundance > 0)
# 长 → 宽:用于排序分析
sp_wide2 <- sp_long |>
pivot_wider(names_from = species, values_from = abundance, values_fill = 0)8.6.3 缺失值处理:drop_na() + fill() + replace_na()
野外观测数据中难免存在缺失值,tidyr 提供了直观的缺失值处理函数:
# 创建含缺失值的时间序列数据
set.seed(42)
df_missing <- tibble(
plot_id = rep(paste0("P", 1:5), each = 3),
month = rep(c("2024-01", "2024-02", "2024-03"), each = 5),
temperature = c(18.2, NA, 19.1, 17.5, 20.3,
NA, 19.8, 20.1, 18.9, 21.2,
19.5, 20.0, NA, 19.2, 22.0)
)
df_missing
# 方法 1:直接去除含缺失值的行
df_missing |> drop_na()
# 方法 2:用前一值填充(时间序列前向填充)
df_missing |> fill(temperature, .direction = "down")
# 方法 3:用后一值填充(后向填充)
df_missing |> fill(temperature, .direction = "up")
# 方法 4:用均值填充
df_missing |> replace_na(list(temperature = mean(df_missing$temperature, na.rm = TRUE)))
# 方法 5:按组填充(不同样方用各自的均值)
df_missing |>
group_by(plot_id) |>
fill(temperature, .direction = "down") |>
ungroup()生态学应用——处理样方调查中的缺失读数:
# 某样方的土壤含水量因仪器故障缺失
# 策略:用相邻两次调查的均值替代
set.seed(42)
soil_data <- tibble(
plot_id = rep("P1", 6),
date = ymd(c("2024-03-01", "2024-03-08", "2024-03-15", "2024-03-22", "2024-03-29", "2024-04-05")),
soil_moisture = c(28.5, 26.2, NA, 24.8, 23.1, 21.5)
)
# 前向填充(用上次数据填充当前缺失)
soil_data |>
fill(soil_moisture, .direction = "down")
# 插值填充(更精确的时间序列插补)
# install.packages("zoo")
library(zoo)
soil_data |>
mutate(soil_moisture = na.approx(soil_moisture))8.6.4 separate() 与 unite():列的拆分与合并
separate() 将一列拆分为多列;unite() 将多列合并为一列:
# 拆分:将日期列拆为年、月、日
survey_with_date <- tibble(
plot_id = paste0("P", 1:5),
survey_date = ymd(c("2024-05-10", "2024-05-12", "2024-05-15", "2024-05-18", "2024-05-20"))
)
survey_with_date |>
separate(survey_date, into = c("year", "month", "day"), sep = "-")
# 合并:将年、月、日合并为日期
tibble(
year = 2024, month = c("03", "04", "05"), day = c("15", "20", "25")
) |>
unite("date", year, month, day, sep = "-") |>
mutate(date = ymd(date))8.7 数据合并
研究中经常需要合并来自不同来源的数据:
# 样方环境数据
plot_env <- tibble(
plot_id = paste0("P", 1:10),
elevation = round(runif(10, 200, 800)),
slope = round(runif(10, 5, 35)),
aspect = sample(c("N", "S", "E", "W"), 10, replace = TRUE)
)
# 将环境数据合并到调查数据
survey_full <- survey |>
left_join(plot_env, by = "plot_id")
head(survey_full)常用的合并方式:
| 函数 | 说明 |
|---|---|
left_join(x, y) |
保留 x 的所有行,匹配 y 的列 |
right_join(x, y) |
保留 y 的所有行 |
inner_join(x, y) |
只保留两边都有的行 |
full_join(x, y) |
保留所有行 |
生态学应用——合并林分调查与环境数据:
# 林分蓄积量数据(注意:缺少 P9, P10)
plot_volume <- tibble(
plot_id = paste0("P", c(1:8, 11:12)),
volume_m3 = round(runif(10, 50, 200), 1)
)
# 合并林分数据(使用 left_join,保留所有调查样方)
survey_with_volume <- survey_full |>
left_join(plot_volume, by = "plot_id")
# 检查哪些样方没有林分数据(NA)
survey_with_volume |>
filter(is.na(volume_m3)) |>
select(plot_id, species) |>
distinct(plot_id)
# 使用 inner_join 只保留有完整数据的样方
survey_complete <- survey_full |>
inner_join(plot_volume, by = "plot_id")
cat("原始样方数:", nrow(survey_full), "\n")
cat("有林分数据的样方数:", nrow(survey_complete), "\n")多键值合并:
当两个数据框需要用多个键合并时,使用 by = c("键1", "键2"):
# 假设环境数据还有生境字段,需要与调查数据匹配
plot_env2 <- tibble(
plot_id = paste0("P", 1:10),
habitat = rep(c("山脊", "山坡", "山谷"), length.out = 10),
elevation = round(runif(10, 200, 800))
)
# 按样方ID和生境两个键合并
survey_checked <- survey |>
left_join(plot_env2, by = c("plot_id", "habitat"))
head(survey_checked)四种合并方式的区别:
| 函数 | 说明 | 适用场景 |
|---|---|---|
left_join(x, y) |
保留 x 的所有行,y 中没有匹配的填 NA | 最常用,用环境属性丰富调查数据 |
right_join(x, y) |
保留 y 的所有行 | 较少用,结果等于 left_join 的反向 |
inner_join(x, y) |
只保留两边都有的行 | 只分析有完整数据的样方 |
full_join(x, y) |
保留所有行,缺失处填 NA | 合并多个数据源时保留全部信息 |
- 键的类型:合并键的数据类型必须一致(如都为字符型),否则 R 会自动转换或报错
- 键的名称:默认按同名列合并;不同名列用
by = c("左表列" = "右表列")指定 - 重复键:右表中有重复键时,左表每行会匹配多条;需注意避免意外膨胀
- NA 处理:NA 不会匹配任何值,包括另一个 NA
8.8 stringr:字符串处理
生态学数据中常有物种学名、样方编号等字符串变量,需要清洗和提取。
8.8.1 常用字符串函数
检测与匹配:
# str_detect():检测是否包含某模式
c("马尾松", "杉木", "Pinus massoniana", "阔叶树") |>
str_detect("松")
# 筛选含"松"的物种名(物种名录清洗)
species_list <- c("马尾松", "杉木", "Pinus massoniana", "Pinus taeda",
"桉树", "Eucalyptus globulus", "阔叶树")
species_list |>
str_subset("松") # 匹配"松"
# str_count():统计模式出现次数
str_count(c("马尾松-杉木-桉树", "马尾松-马尾松", "桉树"), "马尾松")提取与替换:
# str_extract():提取首次匹配
str_extract("P1-马尾松-2024", "[A-Z]\\d") # 提取样方编号
# str_extract_all():提取所有匹配
str_extract_all("P1马尾松P2杉木P3桉树", "[A-Z]\\d") |>
unlist()
# str_replace():替换首次匹配
str_replace("马尾松林", "马尾松", "湿地松")
# str_replace_all():替换所有匹配
str_replace_all("马尾松-杉木-马尾松-桉树", "马尾松", "湿地松")拆分与合并:
# str_split():按分隔符拆分
str_split("P1-马尾松-2024-广西", "-", simplify = TRUE)
# str_c():拼接字符串
str_c("样方", 1:5, sep = "-")
str_c(c("A", "B"), c("1", "2"), sep = "-", collapse = ";")修整与格式化:
# 去除首尾空格
str_trim(" 马尾松 ")
# 去除所有多余空格(包含中间空格)
str_squish(" 马尾 松 ")
# 统一大小写
str_to_upper("massoniana") # 转为大写
str_to_title("pinus massoniana") # 首字母大写8.8.2 生态学应用:物种学名清洗
# 模拟含有录入错误的物种名录
raw_species <- c(
"Pinus massoniana", "Pinus massoniana", "pinus massoniana",
" Cunninghamia lanceolata", "Cunninghamia lanceolata",
"Eucalyptus globulus ", "Eucalyptus globulus",
" Schima superba", "Schima superba", "Shima superba" # 有录入错误
)
# 清洗步骤:去空格 → 统一大小写 → 去重
clean_species <- raw_species |>
str_trim() |> # 去除首尾空格
str_to_lower() |> # 统一转为小写
unique() # 去重
clean_species使用正则表达式处理样方编码:
# 提取样方编号
plot_codes <- c("P01-山脊-A", "P02-山谷-B", "P10-山坡-C", "P03-山脊-D")
# 提取数字编号
str_extract(plot_codes, "P(\\d+)")
str_extract(plot_codes, "(\\d+)-") |> str_remove("-")
# 提取生境类型
str_extract(plot_codes, "(山脊|山坡|山谷)")8.9 forcats:因子变量处理
因子(factor)是 R 中用于表示分类变量的数据类型,在生态学中常用于物种分类、生境类型、调查月份等。
8.9.1 创建与转换因子
# 使用 factor() 创建因子
habitat_factor <- factor(c("山脊", "山谷", "山坡", "山谷", "山脊"))
habitat_factor
# 指定因子水平顺序(影响排列和绘图顺序)
habitat_ordered <- factor(
c("山谷", "山坡", "山脊"),
levels = c("山谷", "山坡", "山脊") # 从低到高排列
)
habitat_ordered8.9.2 常用 forcats 函数
改变水平顺序:
# fct_relevel():手动调整水平顺序(将参照水平放第一位)
survey |>
mutate(habitat = fct_relevel(habitat, "山谷")) |>
pull(habitat) |>
levels()
# fct_reorder():按另一变量统计量排序
survey |>
group_by(habitat) |>
summarise(mean_h = mean(height, na.rm = TRUE)) |>
mutate(habitat = fct_reorder(habitat, mean_h)) |>
pull(habitat) |>
levels()合并与重编码水平:
# fct_collapse():将多个水平合并为一个
survey |>
mutate(
habitat_big = fct_collapse(
habitat,
"低海拔" = c("山谷"),
"中海拔" = c("山坡"),
"高海拔" = c("山脊")
)
) |>
count(habitat_big)处理稀有水平:
# fct_lump_min():将出现次数少于阈值的水平合并为"其他"
survey |>
mutate(species_lumped = fct_lump_min(species, min = 5)) |>
count(species_lumped)
# fct_lump_n():保留出现最多的 n 个,其余归为"其他"
survey |>
mutate(species_top3 = fct_lump_n(species, n = 3)) |>
count(species_top3)8.9.3 生态学应用:生境与物种分析
# 按生境统计并确保生境水平按样地海拔排序
habitat_summary <- survey |>
filter(!is.na(height)) |>
mutate(
# 调整生境水平顺序(山谷→山坡→山脊,体现海拔梯度)
habitat = fct_relevel(habitat, "山谷", "山坡", "山脊")
) |>
group_by(habitat) |>
summarise(
n = n(),
mean_height = round(mean(height), 2),
mean_dbh = round(mean(dbh, na.rm = TRUE), 2)
)
habitat_summary8.10 lubridate:日期时间数据处理
野外观测数据常包含采样日期和时间,lubridate 让日期时间处理变得简单。
8.10.1 解析日期时间
# 常见日期格式的解析
ymd("2024-03-15") # 年-月-日
mdy("03/15/2024") # 月/日/年
dmy("15-03-2024") # 日-月-年
# 解析带时间的格式
ymd_hms("2024-03-15 14:30:00")
ymd_hm("2024-03-15 1430")
# 从字符串向量批量解析
dates <- c("2024-01-05", "2024-02-20", "2024-03-12")
ymd(dates)8.10.2 提取时间成分
survey_date <- ymd("2024-05-20")
# 提取年、月、日、星期
year(survey_date)
month(survey_date)
day(survey_date)
wday(survey_date, label = TRUE) # 星期几(英文标签)
# 提取季度
quarter(survey_date)
# 提取一年中的第几周
epiweek(survey_date)8.10.3 日期时间计算
date1 <- ymd("2024-01-01")
date2 <- ymd("2024-06-15")
# 计算日期间隔(单位:天)
interval(date1, date2) / days(1)
# 计算月份差
interval(date1, date2) / months(1)
# 加减日期
ymd("2024-01-01") + months(3)
ymd("2024-01-01") + years(1)
# 今天是星期几
today()
now()8.10.4 生态学应用:采样日期与季节分析
# 模拟带有采样日期的森林动态监测数据
set.seed(2027)
monitoring <- tibble(
plot_id = paste0("P", 1:20),
survey_date = seq(ymd("2023-01-01"), ymd("2023-12-31"), length.out = 20) |>
sort() |>
ymd(),
new_trees = sample(0:10, 20, replace = TRUE),
mortality = sample(0:3, 20, replace = TRUE)
)
# 添加季节和月份列
monitoring <- monitoring |>
mutate(
month = month(survey_date, label = TRUE),
season = case_when(
month %in% c("12", "1", "2") ~ "冬季",
month %in% c("3", "4", "5") ~ "春季",
month %in% c("6", "7", "8") ~ "夏季",
month %in% c("9", "10", "11") ~ "秋季"
),
year_month = floor_date(survey_date, "month") # 截断到月
)
monitoring
# 按季节统计新增与死亡树木
monitoring |>
group_by(season) |>
summarise(
total_new = sum(new_trees),
total_dead = sum(mortality),
net_change = total_new - total_dead
)8.11 实战:马尾松林调查数据清洗
本节综合运用本章所有技能,对模拟的马尾松林调查数据进行完整的数据清洗流程。
8.11.1 数据背景
某研究团队在广西大学实验林场开展了马尾松混交林样地调查,获得以下数据:
- 乔木层数据(50 株):样方号、树种、树高、胸径、生境类型、土壤 pH
- 环境数据(10 个样方):海拔、坡度、坡向
- 物种名录(含录入错误):中文名和学名混杂
8.11.2 Step 1:加载数据与环境设置
# 加载必要的包
library(tidyverse)
library(lubridate)
# 重新生成原始数据(保证可重复性)
set.seed(2027)
# 乔木层调查数据
survey_raw <- tibble(
plot_id = rep(paste0("P", 1:10), each = 5),
species_cn = sample(
c("马尾松", "杉木", "桉树", "樟树", "楠木",
"马尾松 ", "马尾松林", "杉 木", "桉树 ", " 樟树"),
50, replace = TRUE
),
height = round(rnorm(50, mean = 12, sd = 4), 1),
dbh = round(rnorm(50, mean = 20, sd = 8), 1),
habitat = rep(sample(c("山脊", "山坡", "山谷"), 10, replace = TRUE), each = 5),
soil_ph = round(rnorm(50, mean = 5.5, sd = 0.8), 2)
)
# 人为注入问题
survey_raw$height[c(3, 17, 28)] <- NA
survey_raw$dbh[c(8, 42)] <- NA
survey_raw$soil_ph[15] <- NA
# 物种学名对照表
species_dict <- tribble(
~species_cn_clean, ~species_en, ~family,
"马尾松", "Pinus massoniana", "Pinaceae",
"杉木", "Cunninghamia lanceolata", "Pinaceae",
"桉树", "Eucalyptus globulus", "Myrtaceae",
"樟树", "Cinnamomum camphora", "Lauraceae",
"楠木", "Phoebe zhennan", "Lauraceae"
)
# 环境数据
plot_env <- tibble(
plot_id = paste0("P", 1:10),
elevation = round(runif(10, 200, 800)),
slope = round(runif(10, 5, 35)),
aspect = sample(c("N", "S", "E", "W"), 10, replace = TRUE)
)
cat("=== Step 1: 原始数据 ===\n")
glimpse(survey_raw)8.11.3 Step 2:物种名称清洗
# 物种名录中存在以下问题:
# - 首尾空格("马尾松 ")
# - 多余字符("马尾松林")
# - 空格混乱("杉 木")
survey_clean1 <- survey_raw |>
mutate(
species_cn_clean = species_cn |>
str_trim() |>
str_remove("林$") |>
str_replace_all("\\s+", "")
)
# 清洗前后对比
cat("=== Step 2: 物种名称清洗 ===\n")
survey_clean1 |>
select(species_cn, species_cn_clean) |>
distinct() |>
print(n = Inf)
# 合并学名信息
survey_clean2 <- survey_clean1 |>
left_join(species_dict, by = "species_cn_clean")
head(survey_clean2)8.11.4 Step 3:处理缺失值
# 查看缺失值情况
cat("=== Step 3: 缺失值处理 ===\n")
survey_clean2 |>
summarise(
across(everything(), \\x) sum(is.na(x)))
) |>
pivot_longer(everything(), names_to = "变量", values_to = "NA数量")
# 策略:删除树高和胸径均有缺失的记录;其他用均值填充
survey_clean3 <- survey_clean2 |>
filter(!(is.na(height) & is.na(dbh))) |>
group_by(species_cn_clean) |>
mutate(height = if_else(is.na(height), mean(height, na.rm = TRUE), height)) |>
ungroup() |>
mutate(dbh = coalesce(dbh, median(dbh, na.rm = TRUE))) |>
mutate(soil_ph = coalesce(soil_ph, mean(soil_ph, na.rm = TRUE)))
cat("处理后缺失值:", sum(is.na(survey_clean3$height)), sum(is.na(survey_clean3$dbh)), "\n")8.11.5 Step 4:合并环境数据
在生态学研究中,物种数据和环境数据通常分别采集和存储。物种数据记录每个样方或样地内的物种组成、个体数量、生物量等信息,而环境数据记录样地的海拔、坡度、土壤性质、气候因子等背景信息。将这两类数据合并是进行环境-物种关系分析的前提。数据合并的核心是找到两个数据表之间的”连接键”(join key),通常是样地编号(plot_id)或样方编号(quadrat_id)。
数据合并的类型:
左连接(left_join):保留左表的所有行,右表中没有匹配的行用
NA填充。这是最常用的合并方式,适合”以物种数据为主,补充环境数据”的场景。右连接(right_join):保留右表的所有行,左表中没有匹配的行用
NA填充。内连接(inner_join):只保留两表都有匹配的行,适合”只分析同时有物种和环境数据的样地”的场景。
全连接(full_join):保留两表的所有行,没有匹配的用
NA填充。
生态学应用场景:
假设我们有 50 个样地的树木调查数据(每个样地有多行记录,每行代表一棵树),以及 50 个样地的环境数据(每个样地一行)。我们需要将环境数据合并到树木数据中,以便分析”不同海拔、不同生境类型下的树木生长差异”。
cat("=== Step 4: 合并环境数据 ===\n")
survey_final <- survey_clean3 |>
left_join(plot_env, by = "plot_id") |>
mutate(
basal_area = pi * (dbh / 200)^2,
volume = 0.5 * basal_area * height
)
glimpse(survey_final)代码解析:
left_join(plot_env, by = "plot_id"):将plot_env表(环境数据)按plot_id列合并到survey_clean3表(树木数据)中。由于使用左连接,即使某个样地在plot_env中没有记录,该样地的树木数据仍会保留,只是环境变量为NA。mutate()计算派生变量:basal_area:胸高断面积(m²),公式为 π × (胸径/200)²。胸径单位为 cm,除以 200 转换为半径(m)。volume:树木材积(m³),简化公式为 0.5 × 胸高断面积 × 树高。实际研究中应使用树种特定的材积公式。
合并后的数据检查:
合并数据后,务必检查以下几点:
行数是否正确:左连接后,行数应等于左表的行数。如果行数增加,说明右表中有重复的连接键(一个
plot_id对应多行),需要检查数据是否有误。是否有未匹配的行:检查合并后环境变量是否有
NA。如果有,说明某些样地在环境数据表中缺失,需要补充数据或在分析中排除这些样地。连接键是否唯一:环境数据表中,每个
plot_id应该只有一行。如果有重复,合并后会产生笛卡尔积(每棵树的记录会重复多次),导致数据错误。
常见错误与解决方案:
连接键名称不一致:如果左表的连接键是
plot_id,右表的连接键是PlotID,需要在合并前统一列名,或使用by = c("plot_id" = "PlotID")指定不同的列名。连接键数据类型不一致:如果左表的
plot_id是字符型(“P01”),右表的plot_id是数值型(1),合并会失败。需要先统一数据类型:mutate(plot_id = as.character(plot_id))。连接键有前导/尾随空格:Excel 数据中常见的问题。使用
mutate(plot_id = str_trim(plot_id))去除空格。
实战示例:
假设我们有马尾松林 30 个样地的树木调查数据和环境数据,需要分析”海拔和生境类型对树木生长的影响”:
# 树木数据(每个样地多行)
tree_data <- tibble(
plot_id = rep(paste0("P", 1:30), each = 10), # 每个样地 10 棵树
species = sample(c("马尾松", "杉木", "樟树"), 300, replace = TRUE),
dbh = rnorm(300, mean = 15, sd = 5),
height = rnorm(300, mean = 12, sd = 3)
)
# 环境数据(每个样地一行)
env_data <- tibble(
plot_id = paste0("P", 1:30),
elevation = runif(30, 500, 1500),
habitat = sample(c("阳坡", "阴坡", "山脊"), 30, replace = TRUE),
soil_ph = rnorm(30, mean = 5.5, sd = 0.8)
)
# 合并数据
combined_data <- tree_data |>
left_join(env_data, by = "plot_id") |>
mutate(
basal_area = pi * (dbh / 200)^2,
volume = 0.5 * basal_area * height
)
# 检查合并结果
cat("合并前树木数据行数:", nrow(tree_data), "\n")
cat("合并后数据行数:", nrow(combined_data), "\n")
cat("环境变量缺失数:", sum(is.na(combined_data$elevation)), "\n")
# 按海拔和生境分组统计
summary_stats <- combined_data |>
mutate(elevation_class = cut(elevation, breaks = c(0, 800, 1200, 2000),
labels = c("低海拔", "中海拔", "高海拔"))) |>
group_by(elevation_class, habitat) |>
summarise(
mean_dbh = mean(dbh, na.rm = TRUE),
mean_height = mean(height, na.rm = TRUE),
total_volume = sum(volume, na.rm = TRUE),
.groups = "drop"
)
print(summary_stats)这个流程展示了从数据合并到分组统计的完整过程。掌握数据合并技巧,是进行多源数据整合分析的关键能力。
8.11.6 Step 5:生成汇总报告
数据清洗和合并完成后,生成汇总报告是数据分析流程的重要环节。汇总报告不仅用于检查数据质量,还能快速呈现研究的核心发现,为后续的统计建模和可视化提供基础信息。在生态学研究中,汇总报告通常包括样本量统计、物种多样性指数、环境因子的描述性统计、以及按分组变量(如生境类型、海拔梯度)的对比分析。
生态学汇总报告的核心内容:
- 样本量统计:每个样地的个体数量、物种数量、样地数量等基础信息。
- 多样性指数:Shannon 指数、Simpson 指数、物种丰富度、均匀度等。
- 生物量/材积统计:总生物量、平均生物量、生物量分布等。
- 环境因子统计:海拔范围、土壤 pH 范围、坡度分布等。
- 分组对比:不同生境类型、不同海拔梯度、不同处理组之间的差异。
cat("=== Step 5: 多样性汇总报告 ===\n")
diversity_report <- survey_final |>
group_by(plot_id, habitat, elevation) |>
summarise(
n_individuals = n(),
n_species = n_distinct(species_cn_clean),
shannon_H = -sum((table(species_cn_clean) / n_individuals) *
log(table(species_cn_clean) / n_individuals)),
total_volume = round(sum(volume, na.rm = TRUE), 2),代码解析:
group_by(plot_id, habitat, elevation):按样地编号、生境类型和海拔分组。这意味着后续的统计函数会在每个样地内分别计算。n_individuals = n():统计每个样地的个体数量(行数)。n()是 dplyr 包提供的计数函数,返回当前分组的行数。n_species = n_distinct(species_cn_clean):统计每个样地的物种数量(物种丰富度)。n_distinct()返回不重复的值的数量。shannon_H:计算 Shannon 多样性指数。公式为 H’ = -Σ(p_i × ln(p_i)),其中 p_i 是第 i 个物种的相对多度。这里用table(species_cn_clean) / n_individuals计算每个物种的相对多度,然后乘以其对数并求和。total_volume:计算每个样地的总材积,na.rm = TRUE排除缺失值。
Shannon 指数的生态学意义:
Shannon 指数(H’)综合考虑了物种丰富度和均匀度,是生态学中最常用的多样性指数之一。H’ 值越大,表示群落的多样性越高。典型取值范围:
- H’ < 1:多样性很低,群落由少数优势种主导
- 1 ≤ H’ < 3:多样性中等
- H’ ≥ 3:多样性较高,物种分布较均匀
扩展汇总指标:
除了上述基础指标,还可以计算以下生态学指标:
diversity_report_extended <- survey_final |>
group_by(plot_id, habitat, elevation) |>
summarise(
# 基础统计
n_individuals = n(),
n_species = n_distinct(species_cn_clean),
# 多样性指数
shannon_H = -sum((table(species_cn_clean) / n_individuals) *
log(table(species_cn_clean) / n_individuals)),
simpson_D = sum((table(species_cn_clean) / n_individuals)^2),
evenness = shannon_H / log(n_species), # Pielou 均匀度
# 生物量统计
total_volume = sum(volume, na.rm = TRUE),
mean_volume = mean(volume, na.rm = TRUE),
sd_volume = sd(volume, na.rm = TRUE),
# 树木大小统计
mean_dbh = mean(dbh, na.rm = TRUE),
max_dbh = max(dbh, na.rm = TRUE),
mean_height = mean(height, na.rm = TRUE),
# 优势种识别
dominant_species = names(sort(table(species_cn_clean), decreasing = TRUE))[1],
dominant_abundance = max(table(species_cn_clean)) / n_individuals,
.groups = "drop"
)汇总报告的输出与检查:
生成汇总报告后,应进行以下检查:
数值合理性:Shannon 指数是否在合理范围内?材积是否为正值?物种数量是否小于等于个体数量?
缺失值检查:是否有样地的统计指标为
NA?如果有,需要追溯原因(可能是该样地的原始数据有问题)。异常值检查:是否有样地的统计指标明显偏离其他样地?例如,某个样地的 Shannon 指数远高于其他样地,可能是数据录入错误或该样地确实具有特殊性。
汇总报告的可视化:
生成汇总报告后,通常需要可视化展示:
library(ggplot2)
# 物种丰富度与海拔的关系
ggplot(diversity_report, aes(x = elevation, y = n_species, color = habitat)) +
geom_point(size = 3, alpha = 0.7) +
geom_smooth(method = "lm", se = FALSE) +
labs(x = "海拔 (m)", y = "物种丰富度", color = "生境类型",
title = "物种丰富度沿海拔梯度的变化") +
theme_minimal()
# Shannon 指数的箱线图
ggplot(diversity_report, aes(x = habitat, y = shannon_H, fill = habitat)) +
geom_boxplot(alpha = 0.7) +
geom_jitter(width = 0.2, alpha = 0.3) +
labs(x = "生境类型", y = "Shannon 指数", fill = "生境类型",
title = "不同生境类型的 Shannon 多样性指数") +
theme_minimal() +
theme(legend.position = "none")实战示例:
假设我们完成了马尾松林 30 个样地的数据清洗和合并,现在生成汇总报告并导出为 Excel 文件,供团队成员查看:
library(openxlsx)
# 生成汇总报告
diversity_report <- survey_final |>
group_by(plot_id, habitat, elevation) |>
summarise(
n_individuals = n(),
n_species = n_distinct(species_cn_clean),
shannon_H = round(-sum((table(species_cn_clean) / n_individuals) *
log(table(species_cn_clean) / n_individuals)), 3),
total_volume = round(sum(volume, na.rm = TRUE), 2),
mean_dbh = round(mean(dbh, na.rm = TRUE), 2),
.groups = "drop"
)
# 导出为 Excel
write.xlsx(diversity_report, "output/diversity_summary_report.xlsx")
# 打印前 10 行
print(head(diversity_report, 10))
# 生成总体统计
overall_summary <- diversity_report |>
summarise(
total_plots = n(),
total_species = sum(n_species),
mean_shannon = mean(shannon_H, na.rm = TRUE),
total_volume = sum(total_volume, na.rm = TRUE)
)
cat("\n=== 总体统计 ===\n")
print(overall_summary)这个流程展示了从数据清洗到汇总报告生成的完整过程。掌握汇总报告的生成技巧,是数据分析能力的重要体现,也是向合作者和审稿人展示研究质量的关键环节。 .groups = “drop” ) |> arrange(desc(shannon_H))
diversity_report
## 课后练习
1. **dplyr 基础练习**:使用 `iris` 数据集,用 dplyr 计算每个物种的花瓣长度和宽度的平均值。
2. **宽长格式转换**:将 `iris` 数据集转换为长格式(4 个测量值变成一列 `measurement`,值变成 `value`)。
3. **条件筛选**:筛选出花萼长度大于 6 的记录,按物种分组统计数量。
4. **数据处理流水线**:创建一个完整流水线——筛选 → 新增列(花瓣面积 = 长 × 宽)→ 分组汇总(按物种求均值)→ 排序。
5. **实战练习**:参考本章「实战:马尾松林调查数据清洗」一节,将 `mtcars` 数据集按汽缸数(`cyl`)分组,计算每加仑里程(`mpg`)的平均值和标准差,并将结果保存为 CSV 文件。
::: {.callout-tip}
### 提交要求
将以上练习代码保存为 `.qmd` 文件(至少包含一个代码块和简要说明),渲染为 HTML 后提交到课程 GitHub 仓库。完整的提交流程包括代码编写、本地测试、版本控制和远程同步四个关键步骤。
#### 1. 文件组织与命名规范
作业文件应遵循统一的命名规范,便于教师批改和同学间交流。推荐格式为 `homework-0106-姓名拼音.qmd`,例如 `homework-0106-zhangsan.qmd`。文件应保存在课程仓库的 `homework/` 目录下,并按章节建立子目录(如 `homework/chapter01/`)。
```r
# 在 R 中检查当前工作目录
getwd()
# 如果不在课程仓库根目录,使用 setwd() 切换
setwd("E:/Github/Data-collection-and-preprocessing-2027-Spring")
# 创建作业目录(如果不存在)
if (!dir.exists("homework/chapter01")) {
dir.create("homework/chapter01", recursive = TRUE)
}
2. Quarto 文档渲染与检查
在提交前,务必在本地完成渲染测试,确保代码无误、图表正常显示。使用 RStudio 的 “Render” 按钮或命令行工具:
# 在终端中渲染 Quarto 文档
quarto render homework/chapter01/homework-0106-zhangsan.qmd
# 检查生成的 HTML 文件
ls homework/chapter01/*.html渲染成功后,应检查以下内容:
- 所有代码块是否正常执行(无报错信息)
- 数据框输出是否完整(未被截断)
- 图表是否清晰可见(分辨率适中)
- 中文字符是否正常显示(避免乱码)
3. Git 版本控制流程
课程采用 Git 进行版本管理,标准提交流程如下:
# 1. 查看当前分支和文件状态
git status
# 2. 将作业文件添加到暂存区
git add homework/chapter01/homework-0106-zhangsan.qmd
git add homework/chapter01/homework-0106-zhangsan.html
# 3. 提交到本地仓库(commit message 要清晰)
git commit -m "feat(homework): 完成第1章第6节 dplyr 数据处理练习"
# 4. 推送到远程仓库
git push origin mainCommit Message 规范:
feat: 新增功能或作业fix: 修复错误docs: 文档更新style: 代码格式调整(不影响功能)
4. GitHub Pull Request 创建(协作模式)
如果课程采用 Fork + Pull Request 的协作模式,需要额外步骤:
- Fork 课程仓库到个人 GitHub 账号
- Clone 个人仓库到本地:
git clone https://github.com/你的用户名/仓库名.git - 创建功能分支:
git checkout -b homework-0106 - 完成作业并提交到个人仓库
- 在 GitHub 网页端创建 Pull Request,目标分支为课程仓库的
main
PR 标题示例:[作业提交] 张三 - 第1章第6节 dplyr 练习
PR 描述模板:
## 作业信息
- 章节:第1章第6节 R 数据处理
- 完成日期:2026-04-11
- 学号:202401001
## 完成情况
- [x] 练习1:select 和 filter 基础操作
- [x] 练习2:mutate 创建新列
- [x] 练习3:条件筛选与分组统计
- [x] 练习4:完整数据处理流水线
- [x] 练习5:mtcars 实战练习
## 遇到的问题
在练习4中,最初使用 `summarise()` 时忘记 `group_by()`,导致结果不符合预期。通过查阅文档和调试,最终解决。5. 代码审查注意事项
提交后,教师或助教会进行代码审查(Code Review)。常见反馈包括:
- 代码风格:变量命名是否规范(使用
snake_case) - 注释质量:关键步骤是否有中文注释
- 可重现性:是否使用相对路径(避免硬编码绝对路径)
- 效率问题:是否存在冗余操作(如重复读取数据)
生态学案例:某研究团队在分析全球森林碳汇数据时,因未统一文件路径规范,导致协作者无法重现分析结果。后采用 RStudio Project + 相对路径 + Git 版本控制的标准流程,大幅提升了团队协作效率。该案例强调了规范化提交流程在科研协作中的重要性。
6. 常见问题排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 渲染失败 | 代码块存在语法错误 | 逐块运行代码,定位错误位置 |
| 推送被拒绝 | 远程仓库有新提交 | 先 git pull 再 git push |
| 中文乱码 | 文件编码非 UTF-8 | RStudio 中设置 File > Reopen with Encoding > UTF-8 |
| 图片不显示 | 相对路径错误 | 使用 here::here() 管理路径 |
扩展记录: 2026-04-11 | 扩展者:Clawd | 目标字数:800+