3. 宏基因组物种丰度数据格式与 phyloseq 对象构建
好,咱们进入第三讲。这一讲主要解决一个很实际的问题:你从测序公司或者上游分析流程拿到的那堆表格,到底是个什么结构?怎么把它们变成 R 里能直接分析的 phyloseq 对象?
我刚开始接触宏基因组的时候,也被这些文件搞晕过。OTU 表、ASV 表、分类注释、元数据……文件名五花八门。后来发现,其实核心就三张表。搞懂这三张表,你就掌握了 80% 的数据预处理工作。
3.1 OTU / ASV 表:物种丰度的“数字骨架”
先说说最核心的东西:丰度表。不管你是用 OTU(操作分类单元)还是 ASV(扩增子序列变体),表格的基本形状是一样的。
说白了就是:行是物种,列是样本,中间填的是序列条数或者相对丰度。
| OTU_ID | Sample_A | Sample_B | Sample_C |
|---|---|---|---|
| OTU_001 | 1523 | 0 | 876 |
| OTU_002 | 0 | 2341 | 0 |
| OTU_003 | 409 | 132 | 88 |
注意几个细节:
- 整数还是小数? raw counts(整数)是主流,phyloseq 原生支持。如果是相对丰度,一般会单独标记。
- 列名必须是唯一的样本编号。 我见过有人用“Control-1”“Control-2”,但有些函数自动去特殊字符,踩过坑。我的建议是:统一用字母数字和下划线,比如
C1、T2。 - 行名必须是唯一的 OTU/ASV 标识符。 不要用属名或者种名当行名,不然合并分类注释时会乱套。
💡 导入前先检查:
用 read.table() 或 read.csv() 读取丰度表时,务必设置 row.names = 1,确保第一列成为行名。
3.2 分类学注释表:从 Kingdom 到 Species 的“翻译器”
丰度表告诉你有多少条序列,但没告诉你这些序列是什么微生物。分类学注释表就是干这个的。
格式很简单:行也是 OTU/ASV ID,列是各个分类层级。
| OTU_ID | Kingdom | Phylum | Class | Order | Family | Genus | Species |
|---|---|---|---|---|---|---|---|
| OTU_001 | Bacteria | Firmicutes | Bacilli | Lactobacillales | Lactobacillaceae | Lactobacillus | Lactobacillus_acidophilus |
| OTU_002 | Bacteria | Bacteroidetes | Bacteroidia | Bacteroidales | Bacteroidaceae | Bacteroides | Bacteroides_fragilis |
这里有两个坑:
- 缺失层级怎么办? 有些注释工具会填
NA或者Unassigned。我建议保留这些值,不要强行设成"Unknown",后面绘图时可以用 phyloseq 的函数统一处理。 - 层级名称必须统一。 比如 Phylum 列不要写成“phylum”或“门”。大小写无所谓,但最好固定一种命名风格。我个人习惯全部首字母大写。
⚠️ 重要:注释表里的 OTU/ASV ID 必须与丰度表完全一致。
比如丰度表里有 OTU_001,分类表里也要有 OTU_001,一个字母都不能差。我曾经被空格坑过——一个表里是 " OTU_001"(前导空格),半天找不到错。
3.3 元数据表格:实验设计的“说明书”
这张表描述的是样本本身的信息。为什么需要它?因为没有它,你没法做分组比较。
| SampleID | Group | TimePoint | Treatment |
|---|---|---|---|
| Sample_A | Control | Day0 | None |
| Sample_B | Treatment | Day7 | Antibiotic |
| Sample_C | Control | Day7 | None |
写元数据时,注意以下几点:
- 行名必须是样本名,且与丰度表的列名一一对应。 这是最容易出错的地方。
- 列可以是任意类型:因子、数值、字符。 分组变量建议设为因子,R 里很多函数默认按因子处理。
- 避免在元数据里放多余的空格或特殊字符。 比如
"Control "(末尾空格)会被认为是另一个组。
📌 三张表的关系总结:
- 丰度表:行 = OTU/ASV ID,列 = 样本名
- 分类表:行 = OTU/ASV ID,列 = 分类层级
- 元数据表:行 = 样本名,列 = 实验变量
三者靠行名和列名连接起来,形成一个完整的 phyloseq 对象。
3.4 phyloseq 对象构建:把三张表“粘”在一起
好了,三张表准备好了。接下来就是见证奇迹的时刻——用 phyloseq 包把它们变成一个统一的对象。
首先,确保你已经安装了 phyloseq 包:
install.packages("BiocManager")
BiocManager::install("phyloseq")
然后,导入数据并构建对象。我个人习惯分四步走:
- 读入丰度表并转为 otu_table 对象
library(phyloseq)
otumat <- read.table("otu_table.txt", header = TRUE, row.names = 1, sep = "\t")
# 转成矩阵是必须的,phyloseq 要求输入是矩阵
otumat <- as.matrix(otumat)
OTU <- otu_table(otumat, taxa_are_rows = TRUE)
- 读入分类注释表并转为 tax_table 对象
taxmat <- read.table("taxonomy_table.txt", header = TRUE, row.names = 1, sep = "\t")
# 同样要转成矩阵
taxmat <- as.matrix(taxmat)
TAX <- tax_table(taxmat)
- 读入元数据并转为 sample_data 对象
sampledata <- read.table("metadata.txt", header = TRUE, row.names = 1, sep = "\t")
SAM <- sample_data(sampledata)
- 合并为 phyloseq 对象
ps <- phyloseq(OTU, TAX, SAM)
# 查看对象摘要
ps
💡 我的习惯:构建完成后立刻运行 nsamples(ps) 和 ntaxa(ps) 检查样本数和物种数,快速确认数据是否完整。
有时候你可能还需要一棵系统发育树(系统进化树),那对应的是 phy_tree() 插槽。不过对于入门阶段,上面这三步已经能支撑绝大多数可视化任务了。
3.5 常见问题与避坑
这些年我用 phyloseq 处理过几十个数据集,遇到过不少小问题。列几个常见的,你遇到了别慌。
- 报错:OTU table 和 sample names 不匹配? 检查丰度表的列名与元数据表的行名是否完全一致,包括大小写。
- 报错:tax_table 中的 NA 导致函数出错? 可以先用
tax_table(ps)[is.na(tax_table(ps))] <- "Unassigned"处理一下再做后续分析。 - 我的数据不是 counts,是相对丰度? phyloseq 不限制数值类型,但有些分析(如 alpha 多样性)假设输入是整数 counts。如果你的数据是相对丰度,可以在分析时显式声明。
⚠️ 我曾经踩过一个坑:用 Excel 编辑过 OTU 表后,保存为文本文件时 Excel 自动把某些长数字变成了科学计数法。结果 R 读进来后数值变了。我的对策是——尽量用 read.table() 的 colClasses 参数指定每一列的类型,避免 R 自动猜测。
构建好 phyloseq 对象之后,下一步就可以用它来做各种可视化了。不过那是后面几章的内容。这一章先到这里——记住,数据清洗和对象构建是分析的地基,地基打不稳,楼上再漂亮也白搭。