数据清洗基础:处理缺失值、删除重复行、统一物种/基因命名、数据类型转换
走到这一步,你的丰度数据已经读进来了。但你得做好心理准备——原始数据从来不会干干净净。我见过太多人拿到的数据里,有NA、有空字符,甚至同一物种在不同行里叫法都不一样。说实话,这要是直接往下分析,结果能把你引到沟里去。
所以本章就是干这个的,把数据洗白。我带你过四个核心步骤:处理缺失值、删除重复行、统一命名、转换数据类型。按我自己的习惯,这四步走完,才算真正拿到可以用的数据。
3.1 处理缺失值
缺失值是最常见的脏数据。在丰度数据里,一个物种在某些样本中计数为0,那是正常;但如果整条记录都是NA,或者某一列关键信息丢失,就得处理了。
我常用这样几个函数来定位缺失值:
# 检查数据中是否有缺失值
any(is.na(df))
# 按列统计缺失数量
colSums(is.na(df))
# 查看缺失值的位置(行、列索引)
which(is.na(df), arr.ind = TRUE)
为什么会这样?因为R里很多模型和统计函数,碰到NA会直接报错。我刚开始学R时吃过这个亏,跑到一半程序挂了,折腾半天才发现是因为某列有一个NA。
处理方式通常有三种,我列个表,你对比着看:
| 方法 | 适用场景 | R代码 |
|---|---|---|
| 删除含NA的行 | 缺失值较少(<5%)且随机分布 | na.omit(df) |
| 用均值/中位数填充 | 连续性丰度值,缺失不多 | df[is.na(df)] <- mean(df, na.rm = TRUE) |
| 用0填充 | 丰度数据中缺失视为未检出 | df[is.na(df)] <- 0 |
| 按样本分组填充 | 需要考虑样本分组差异时 | dplyr::group_by() + mutate() |
我个人最常用的是0填充。为什么?丰度数据里NA很多时候代表「测到了但没检测出」,设为0更符合生物学意义。当然,你得先确认这个假设对你的数据成立。
3.2 删除重复行
重复行的问题,在手动拼接数据或者从不同平台合并数据时特别常见。嗯,这里要注意,重复可能有两种情况:
- 整行完全重复——所有列都一样。
- 关键列重复——比如物种名一样,但其他列数据不同。
先看看怎么处理第一种:
# 找出重复行
duplicated(df)
# 查看哪些行重复
df[duplicated(df), ]
# 删除重复行,保留第一次出现的记录
df_unique <- df[!duplicated(df), ]
# 用dplyr语法更直观
library(dplyr)
df_unique <- df %>% distinct()
第二种情况更棘手。我曾经碰到过:同一物种在Excel里被录入两遍,计数还不一样。这时你要根据业务逻辑决定保留哪个版本。比如我习惯按「版本日期最近的那个」保留。
# 按物种名分组,保留版本最新的行
df_clean <- df %>%
group_by(species) %>%
slice_max(order_by = version_date, n = 1) %>%
ungroup()
View()肉眼扫一遍。自动删除可能会误删,特别当你有意保留的不同样本在同一物种下时。
3.3 统一物种/基因命名
这个问题看似小,实则坑大。同一个物种,有人写"E. coli",有人写"Escherichia coli",还有人写成"Escherichia_coli"——这些在R里会被当成三个不同的物种。
说白了,这就是命名不规范造成的。我一般在清洗步骤里做两件事:
- 去除首尾空格
- 统一大小写和分隔符
# 去除空格
df$species <- trimws(df$species)
# 统一为小写
df$species <- tolower(df$species)
# 替换分隔符(如将空格换成下划线)
df$species <- gsub(" ", "_", df$species)
# 或者,你也可以用stringr包做类似操作
library(stringr)
df$species <- str_to_lower(df$species)
df$species <- str_replace_all(df$species, " ", "_")
遇到同义名,比如"B. subtilis"和"Bacillus subtilis"这种,你需要一个对照表。我更推荐使用权威数据库的ID来做映射,比如NCBI Taxonomy ID。
# 示例:用对照表映射统一名称
lookup_table <- data.frame(
raw_name = c("B. subtilis", "Bacillus subtilis"),
uniform_name = c("Bacillus_subtilis", "Bacillus_subtilis")
)
df$species <- lookup_table$uniform_name[
match(df$species, lookup_table$raw_name)
]
关键认知:命名统一这件事,做在前面的投入产出比极高。你想想看,如果这一步漏了,后面所有涉及物种分组、统计检验、可视化的结果都有问题。
3.4 数据类型转换
这一步是很多R新手容易忽略的。读进来的数据,R常会自动推断类型。但推断的结果往往不是你想要的。
比如,物种ID列被当成了数值型;分组标签被当成了字符型;丰度值被当成了整数……这些都会坑到你后续的分析。
# 查看每列的数据类型
str(df)
# 将物种名列转为字符型(如果不是的话)
df$species <- as.character(df$species)
# 将分组列转为因子(用于分组分析)
df$group <- as.factor(df$group)
# 将丰度列转为数值型
df$abundance <- as.numeric(df$abundance)
# 用dplyr的mutate()配合across()批量转
df <- df %>%
mutate(across(where(is.character), as.factor))
我记得接手一个项目,原始数据里的丰度列因为混入了字符串,read.csv() 直接把它整列读成了字符型。我一跑统计,一直报错。花了半小时才发现,原来是某一行写了个"NA"字符串,不是真正的缺失值标记。从那以后,我每次读完数据第一件事就是跑str()确认类型。
as.numeric()对因子型变量直接转换,得到的是内部的整数编码,不是原始数值。碰到这种情况,先用as.character()转成字符,再转数值。
整个数据清洗的流程,我画了一张图,帮你理清脉络:
这四步做完,你可以用summary()和str()再确认一遍整体状况。没问题了,数据就可以放心拿去做下一步分析了。
嗯,这就是数据清洗的全貌。看似琐碎,但每一行代码都是在给你的分析结果上保险。别嫌麻烦,花在这里的时间,后面都会十倍还给你。