3. 宏基因组物种丰度数据格式与 phyloseq 对象构建

好,咱们进入第三讲。这一讲主要解决一个很实际的问题:你从测序公司或者上游分析流程拿到的那堆表格,到底是个什么结构?怎么把它们变成 R 里能直接分析的 phyloseq 对象?

我刚开始接触宏基因组的时候,也被这些文件搞晕过。OTU 表、ASV 表、分类注释、元数据……文件名五花八门。后来发现,其实核心就三张表。搞懂这三张表,你就掌握了 80% 的数据预处理工作。

宏基因组物种丰度数据输入结构 OTU / ASV 丰度表 行:OTU/ASV ID 列:样本名 分类学注释表 行:OTU/ASV ID Kingdom → Species 元数据表 行:样本名 列:实验分组 / 条件 phyloseq 对象 otu_table() tax_table() sample_data() 导出整合数据 ps 对象 用于下游分析 ⚠ 三张表缺一不可。OTU/ASV 表与分类学注释表通过 OTU/ASV ID 关联,元数据表以“样本名”为键。 phyloseq 对象就是这三张表加上一棵系统发育树的“容器”。

3.1 OTU / ASV 表:物种丰度的“数字骨架”

先说说最核心的东西:丰度表。不管你是用 OTU(操作分类单元)还是 ASV(扩增子序列变体),表格的基本形状是一样的。

说白了就是:行是物种,列是样本,中间填的是序列条数或者相对丰度。

OTU_IDSample_ASample_BSample_C
OTU_00115230876
OTU_002023410
OTU_00340913288

注意几个细节:

  • 整数还是小数? 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_IDKingdomPhylumClassOrderFamilyGenusSpecies
OTU_001BacteriaFirmicutesBacilliLactobacillalesLactobacillaceaeLactobacillusLactobacillus_acidophilus
OTU_002BacteriaBacteroidetesBacteroidiaBacteroidalesBacteroidaceaeBacteroidesBacteroides_fragilis

这里有两个坑:

  • 缺失层级怎么办? 有些注释工具会填 NA 或者 Unassigned。我建议保留这些值,不要强行设成 "Unknown",后面绘图时可以用 phyloseq 的函数统一处理。
  • 层级名称必须统一。 比如 Phylum 列不要写成“phylum”或“门”。大小写无所谓,但最好固定一种命名风格。我个人习惯全部首字母大写。

⚠️ 重要:注释表里的 OTU/ASV ID 必须与丰度表完全一致。

比如丰度表里有 OTU_001,分类表里也要有 OTU_001,一个字母都不能差。我曾经被空格坑过——一个表里是 " OTU_001"(前导空格),半天找不到错。

3.3 元数据表格:实验设计的“说明书”

这张表描述的是样本本身的信息。为什么需要它?因为没有它,你没法做分组比较。

SampleIDGroupTimePointTreatment
Sample_AControlDay0None
Sample_BTreatmentDay7Antibiotic
Sample_CControlDay7None

写元数据时,注意以下几点:

  • 行名必须是样本名,且与丰度表的列名一一对应。 这是最容易出错的地方。
  • 列可以是任意类型:因子、数值、字符。 分组变量建议设为因子,R 里很多函数默认按因子处理。
  • 避免在元数据里放多余的空格或特殊字符。 比如 "Control "(末尾空格) 会被认为是另一个组。

📌 三张表的关系总结:

  • 丰度表:行 = OTU/ASV ID,列 = 样本名
  • 分类表:行 = OTU/ASV ID,列 = 分类层级
  • 元数据表:行 = 样本名,列 = 实验变量

三者靠行名和列名连接起来,形成一个完整的 phyloseq 对象。

3.4 phyloseq 对象构建:把三张表“粘”在一起

好了,三张表准备好了。接下来就是见证奇迹的时刻——用 phyloseq 包把它们变成一个统一的对象。

首先,确保你已经安装了 phyloseq 包:

install.packages("BiocManager")
BiocManager::install("phyloseq")

然后,导入数据并构建对象。我个人习惯分四步走:

  1. 读入丰度表并转为 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)
  1. 读入分类注释表并转为 tax_table 对象
taxmat <- read.table("taxonomy_table.txt", header = TRUE, row.names = 1, sep = "\t")
# 同样要转成矩阵
taxmat <- as.matrix(taxmat)
TAX <- tax_table(taxmat)
  1. 读入元数据并转为 sample_data 对象
sampledata <- read.table("metadata.txt", header = TRUE, row.names = 1, sep = "\t")
SAM <- sample_data(sampledata)
  1. 合并为 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 对象之后,下一步就可以用它来做各种可视化了。不过那是后面几章的内容。这一章先到这里——记住,数据清洗和对象构建是分析的地基,地基打不稳,楼上再漂亮也白搭。