数据读入:把原始数据请进 R 的世界
数据读入这事,说简单也简单,说坑也多。我早些年在处理一批宏基因组项目时,就因为读入时没注意编码,三天的分析全白费了。嗯,从那以后我对这步格外小心。
你手里拿到的丰度数据,格式五花八门。最常见的有 CSV、TSV,也有 Excel 表格,偶尔会碰到上 GB 的大文件。本章我把它们的读入方法逐一过一遍,最后再教你怎么检查读进来的数据是否完整。
2.1 readr 包:CSV 和 TSV 的优雅读法
readr 是 tidyverse 家族的一员,读表快,还不自动把字符串转成因子。我用它主要是因为它的输出是 tibble,打印起来干净,不会把终端刷爆。
读 CSV 文件,核心函数是 read_csv():
library(readr)
# 读取丰度矩阵
abundance <- read_csv("data/abundance.csv")
# 读取物种注释
taxonomy <- read_csv("data/taxonomy_list.csv")
如果你手里的文件是用制表符分隔的,那就用 read_tsv():
# 读取 TSV 格式的丰度数据
abundance_tsv <- read_tsv("data/abundance.tsv")
小经验:我个人习惯在 read_csv() 里设置 locale = locale(encoding = "UTF-8")。有些 Windows 环境下导出的 CSV 是 GBK 编码,不指定 locale 的话,中文列名就会变成乱码。我在项目中遇到过三次这样的问题,每次都折腾了大半天。
还有个常用参数 col_types,可以手动指定每列的类型,避免 R 猜错:
# 指定前两列为字符,后面全是双精度
col_spec <- cols(
OTU_ID = col_character(),
Taxonomy = col_character(),
.default = col_double()
)
abundance <- read_csv("data/abundance.csv", col_types = col_spec)
2.2 readxl 包:读取 Excel 文件
Excel 在生物信息学领域很普及,合作方经常把数据整理在 .xlsx 里。readxl 包不需要装 Java,也不用写复杂的库路径,直接一行就能读。
基本用法:
library(readxl)
# 读取第一个工作表
abundance_xl <- read_excel("data/abundance.xlsx")
# 读取指定工作表
abundance_sheet2 <- read_excel("data/abundance.xlsx", sheet = "OTU_Table")
有时 Excel 表头不全,前几行是注释,这时用 skip 参数跳过:
# 跳过前 3 行注释
abundance_clean <- read_excel("data/abundance.xlsx", skip = 3)
注意:我曾经因为没检查 Excel 文件是否有合并单元格,读进来的数据一半是 NA。readxl 不会报错合并单元格,但会把它变成缺失值。建议读完后先瞄一眼行数和列数。
如果你要读取所有工作表,可以用 excel_sheets() 先查看工作表名称,再循环读取:
sheets <- excel_sheets("data/abundance.xlsx")
sheets
# 假设返回 [1] "Sheet1" "Sheet2" "Metadata"
all_data <- lapply(sheets, read_excel, path = "data/abundance.xlsx")
names(all_data) <- sheets
2.3 data.table 包:大文件的性能之选
丰度数据一但上了几万 OTU × 几百样本,直接用 readr 就会慢得让你怀疑人生。这时候就该 data.table 出场了。
data.table 的核心读入函数是 fread(),你看这个 "f" 代表 "fast",它真的很快:
library(data.table)
# 读取大 CSV
abundance_dt <- fread("data/abundance.csv")
# 自动识别分隔符
# fread 能猜出是逗号还是制表符,不用你操心
再多讲几个我常用的参数:
nrows:先读几行看看结构,确认没问题了再全读select:只读你需要的列,能省很多内存na.strings:把 "NA" "null" 等统一转成 R 的 NAdata.table = FALSE:返回普通 data.frame(如果你不习惯 data.table 语法)
# 只读前 100 行 + 特定列
abundance_sample <- fread("data/abundance.csv",
nrows = 100,
select = c("OTU_ID", "Sample1", "Sample2", "Sample3"))
# 自定义缺失值标记
abundance_fixed <- fread("data/abundance.csv",
na.strings = c("", "NA", "null", "-"))
技巧:如果文件实在太大,考虑用 fread() 的 nThread 参数启用多核。我在处理一个 3 GB 的宏基因组丰度表时,开 8 个线程,从原来的 40 秒降到 8 秒。效果很直观。
这里简单对比下三种读入工具:
| 包 | 核心函数 | 速度 | 适用场景 | 返回值类型 |
|---|---|---|---|---|
| readr | read_csv / read_tsv | 中等 | 一般大小 CSV/TSV | tibble |
| readxl | read_excel | 较慢 | Excel 格式 | tibble |
| data.table | fread | 极快 | 大文件、多格式 | data.table / data.frame |
推荐原则:80% 的日常工作用 readr 就够了。只有文件超过 500 MB 或者你想同时读取很多个文件时,再换 data.table。不要为了快而快,可读性和生态也很重要。
2.4 检查数据完整性:别让脏数据骗了你
数据读进来了,但不代表它就是干净的。我在这步吃过太多亏了,所以现在每读一个文件,都会用下面这些方法检查一遍。
2.4.1 查看行数和列数
dim(abundance)
# 返回 [1] 1000 15
# 1000 行(OTU),15 列(OTU_ID + 14 个样本)
dim() 给出的是最直观的数字。如果你预期是 1050 行但只读进来 1000 行,说明文件可能有空行或者分隔符问题。
2.4.2 检查缺失值
# 统计每列的缺失值数量
colSums(is.na(abundance))
# 看看哪些行有 NA
which(rowSums(is.na(abundance)) > 0)
丰度数据里偶尔出现 NA 是正常的——可能是测序深度太低导致某些 OTU 在某样本中没有被测到。但一旦大量出现 NA,就可能是读入时指定错了 na.strings。
坑:我记得有一次合作方在 Excel 里把缺失的丰度值写成了 "0",还把真正的零写成了 "0.00"。读进来后我总以为全有数据,后来才发现有一半的丰度其实是零。所以读完后最好看下丰度值的分布,用 summary() 抓一下异常。
2.4.3 检查列名是否有乱码或空格
names(abundance)
# 正常应该是 "OTU_ID" "Sample_1" "Sample_2" ...
如果看到 Sample 1(中间有空格)或者 \ufeffSample1(BOM 头),那就要处理掉。我一般用 janitor::clean_names() 自动统一列名格式。
2.4.4 检查数据类型是否一致
str(abundance)
# 检查每列的类型:字符、数值、整数?
丰度值必须是数值型。如果某列的类型变成了 character,说明这一列里有非数字字符,比如 "N/A" 或者 "1,000"。这种情况出现的频率远比你想象的高。
2.4.5 一个快速检查的函数
我把上面几步整合成了一个函数,每次读入后都会跑一遍:
check_data <- function(df, name = "data") {
cat("=== 数据完整性检查:", name, "===\n")
cat("行数:", nrow(df), " 列数:", ncol(df), "\n")
cat("缺失值总数:", sum(is.na(df)), "\n")
cat("列名: ", paste(head(names(df)), collapse = ", "), "...\n")
cat("各列类型:\n")
print(sapply(df, class))
}
check_data(abundance, "丰度表")