数据清洗基础:处理缺失值、删除重复行、统一物种/基因命名、数据类型转换

走到这一步,你的丰度数据已经读进来了。但你得做好心理准备——原始数据从来不会干干净净。我见过太多人拿到的数据里,有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更符合生物学意义。当然,你得先确认这个假设对你的数据成立。

警告:不要盲目用na.omit()删除所有含NA的行。如果数据稀疏,删除可能损失大量信息。我见过有人删完之后原本1000个物种只剩200个,整个分析全废了。

3.2 删除重复行

重复行的问题,在手动拼接数据或者从不同平台合并数据时特别常见。嗯,这里要注意,重复可能有两种情况:

  1. 整行完全重复——所有列都一样。
  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里会被当成三个不同的物种。

说白了,这就是命名不规范造成的。我一般在清洗步骤里做两件事:

  1. 去除首尾空格
  2. 统一大小写和分隔符
# 去除空格
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()转成字符,再转数值。

整个数据清洗的流程,我画了一张图,帮你理清脉络:

数据清洗核心流程 原始丰度数据(原始数据) 步骤1:处理缺失值(缺失值) na.omit / 0填充 / 均值填充 步骤2:删除重复行(重复行) duplicated / distinct 步骤3:统一命名(统一命名) trimws / tolower / 映射表 步骤4:数据类型转换(类型转换) as.character / as.factor / str 注意:四步按顺序执行 缺失值处理 → 去重 → 命名 → 类型

这四步做完,你可以用summary()和str()再确认一遍整体状况。没问题了,数据就可以放心拿去做下一步分析了。

嗯,这就是数据清洗的全貌。看似琐碎,但每一行代码都是在给你的分析结果上保险。别嫌麻烦,花在这里的时间,后面都会十倍还给你。