4. dplyr 数据清洗:filter、select、mutate、group_by、summarise
数据清洗这步,我做了这么多年项目,每次都要花掉 60% 以上的时间。说白了,数据不洗没法用。R 语言里 dplyr 是清洗的利器,今天我们一口气把五个核心函数讲透。
先看整体逻辑: 原始数据 → 过滤行(filter) → 选列(select) → 创建新列(mutate) → 分组(group_by) → 汇总(summarise)。不绕弯子,直接上图。
4.1 filter – 按条件筛选行
filter() 好比筛子,只保留满足条件的行。我个人习惯把逻辑条件写在逗号后面,多个条件是「与」关系。
library(dplyr)
# 筛选 drug_approved == "是" 且 target_count > 5 的药物
drug_clean <- drug_raw %>%
filter(drug_approved == "是", target_count > 5)
filter(drug_approved == "是" | target_count > 5) 忘了加括号,结果数据量少了一半。建议不确定优先级时就加括号,filter((drug_approved == "是") | (target_count > 5))。
另外,filter() 里也能用 %in%、between()、is.na() 这些辅助函数,很顺手。
4.2 select – 挑选需要的列
数据量大了以后,列太多看着头晕。用 select() 只留关键字段。
# 只保留药物名、批准状态、靶点数量
drug_selected <- drug_raw %>%
select(drug_name, drug_approved, target_count)
# 也可以用 - 去掉不要的列
drug_selected <- drug_raw %>%
select(-c(drug_id, create_date, update_date))
select() 还支持很多帮手函数,比如 starts_with()、ends_with()、contains()。我在药物知识图谱项目里,经常用 select(drug_name, starts_with("target_")) 把靶点信息一网打尽。
4.3 mutate – 生成新列
清洗中经常要计算新的指标,比如药物靶点密度,或者把分类变量转成数值。这时候 mutate() 就上场了。
# 计算靶点密度: 靶点数量 / 分子量
drug_mutated <- drug_raw %>%
mutate(target_density = target_count / molecular_weight)
mutate() 里可以引用当前数据框的其他列,但不要在同一行 mutate 里交叉引用后续生成的新列。需要链式操作就用两次 mutate。
我经常搭配 if_else() 做条件赋值,比如给靶点数量打标签:
drug_mutated <- drug_raw %>%
mutate(target_level = if_else(target_count > 10, "high", "low"))
4.4 group_by – 分组
group_by() 不改变数据本身,它只是给数据框打上分组标签,后续的操作(如 summarise()、mutate())就会按组执行。
# 按药物批准状态分组
drug_grouped <- drug_raw %>%
group_by(drug_approved)
分组后可以用 ungroup() 解除分组。我记得刚用 dplyr 时总忘记取消分组,导致后续计算一直按之前的分组跑,结果全是错的。所以养成好习惯:分组汇总完立刻取消分组。
4.5 summarise – 汇总统计
summarise() 和 group_by() 是绝配。它们一起能快速产出按组的统计量,比如均值、中位数、计数等。
# 按批准状态分组,计算靶点数量的均值、标准差、个数
drug_summary <- drug_raw %>%
group_by(drug_approved) %>%
summarise(
mean_target = mean(target_count, na.rm = TRUE),
sd_target = sd(target_count, na.rm = TRUE),
n = n()
) %>%
ungroup()
这里 n() 是 dplyr 自带函数,返回每组行数。如果数据里有缺失值,记得加 na.rm = TRUE,不然结果全是 NA。我碰过一回,查了半天才发现是缺失值没去掉。
4.6 全部串起来:一个真实案例
假设我们有一个药物‑靶点关系数据框 drug_target,包含字段:drug_name、target_gene、affinity(亲和力)、approved。现在要找出已批准药物中亲和力大于 7 的靶点,按药物分组统计靶点数量,最后按数量降序排列。
result <- drug_target %>%
filter(approved == "是", affinity > 7) %>%
select(drug_name, target_gene, affinity) %>%
mutate(affinity_class = if_else(affinity >= 9, "strong", "moderate")) %>%
group_by(drug_name) %>%
summarise(
strong_targets = sum(affinity_class == "strong"),
moderate_targets = sum(affinity_class == "moderate"),
total_targets = n()
) %>%
ungroup() %>%
arrange(desc(total_targets))
你看,用 %>% 管道符串起来,逻辑清楚,每一行只做一个动作。我写的所有清洗脚本都保持这种风格,维护起来特别轻松。
常用函数速查表
| 函数 | 作用 | 典型用法 |
|---|---|---|
| filter() | 行过滤 | filter(approved == "是", age > 30) |
| select() | 列选择 | select(drug, target, affinity) |
| mutate() | 新建列 | mutate(score = affinity * weight) |
| group_by() | 分组 | group_by(drug_class) |
| summarise() | 汇总 | summarise(mean = mean(score)) |
我的经验: 虽然单个函数很简单,但组合起来威力巨大。我过去做过的药物特征分析,90% 的数据清洗都用这五个函数搞定。多用几次,肌肉记忆就出来了。