4. BIOM 格式版本差异:BIOM 1.0 vs 2.0,HDF5 vs JSON
好,咱们聊聊版本。BIOM 格式从 1.0 到 2.0 的变化,说白了就是一次脱胎换骨。我刚接触时也觉得,不就是个格式嘛,升级就升级呗。
结果第一次处理一个 2GB 的 JSON 文件,直接把我笔记本内存撑爆了。嗯,从那以后,我深刻理解了为什么要有 2.0。
4.1 核心差异在哪?
最根本的区别,就是底层存储格式换了。
- BIOM 1.0:只能基于 JSON(纯文本)。
- BIOM 2.0:强制基于 HDF5(二进制)。
你想想看,JSON 是给人读的,但机器读起来效率就低了。而 HDF5 是给机器读的,人类想直接看?没门。
我的经验是:小数据集(比如几百个 OTU,几十个样本)用 1.0 JSON 完全没问题。一旦样本数上千,或者要做大规模宏基因组分析,必须上 2.0 HDF5。
4.2 BIOM 1.0:轻量但膨胀
BIOM 1.0 的结构很简单,就是标准的 JSON 对象。核心字段不多:
{
"id": "MyTable",
"format": "Biological Observation Matrix 1.0.0",
"format_url": "http://biom-format.org",
"type": "OTU table",
"generated_by": "QIIME 1.9.1",
"date": "2020-01-15",
"matrix_type": "dense",
"matrix_element_type": "int",
"shape": [5, 3],
"data": [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14]],
"rows": [
{"id": "OTU1", "metadata": {"taxonomy": ["Bacteria", "Firmicutes"]}},
{"id": "OTU2", "metadata": {"taxonomy": ["Bacteria", "Bacteroidetes"]}}
],
"columns": [
{"id": "Sample1", "metadata": {"host": "Human", "body_site": "gut"}},
{"id": "Sample2", "metadata": null}
]
}
看着挺清晰,对吧?但这里有个坑:metadata 重复。每个 OTU 的 taxonomy 信息都会重复存储,哪怕 10000 个 OTU 都属于同一个门。文件体积就这么被撑大了。
曾经有一次,我用 1.0 格式导出一个 500 个样本、10000 个 OTU 的丰度表,JSON 文件直接飙到 1.5GB。加载花了 15 分钟,解析又花了 10 分钟。那时候我就想,这必须得改。
4.3 BIOM 2.0:高效但复杂
BIOM 2.0 改用 HDF5,这是个什么概念?
HDF5 是一种分层数据格式,它把数据存在一个文件系统中。你可以把它想象成一个压缩的、带索引的小型数据库。
2.0 的 biom 文件,用 HDF5 工具查看,结构大概是这样:
/
├── observation
│ ├── matrix
│ │ ├── data (dataset, int)
│ │ ├── indices (dataset, int)
│ │ └── indptr (dataset, int)
│ └── metadata
│ └── taxonomy (dataset, variable-length string)
├── sample
│ ├── matrix
│ │ ├── data
│ │ ├── indices
│ │ └── indptr
│ └── metadata
│ └── host (dataset, string)
├── observation_group
│ └── group_metadata (dataset, ...)
└── sample_group
└── group_metadata (dataset, ...)
你看到了什么?数据被拆成了 CSR(Compressed Sparse Row)格式 存储。data、indices、indptr 三剑客,专门对付稀疏矩阵。要知道,丰度表绝大多数都是稀疏的(大部分 OTU 在大部分样本里是 0),这个设计太聪明了。
我个人的习惯是:所有新项目都默认生成 2.0 HDF5 格式。只有在跟老软件(比如 QIIME 1)交互时,才转成 1.0 JSON。
4.4 文档级对比
| 对比维度 | BIOM 1.0 (JSON) | BIOM 2.0 (HDF5) |
|---|---|---|
| 底层格式 | 纯文本 | 二进制 |
| 文件大小 | 大(膨胀) | 小(压缩) |
| 读取速度 | 慢(需全量加载) | 快(支持随机读取) |
| 稀疏矩阵支持 | 支持但不高效 | 原生高效存储 |
| 元数据 | 重复存储 | 分级去重存储 |
| 兼容性 | 几乎所有旧工具 | 新工具、Python/R |
| 人类可读 | 是(可用文本编辑器打开) | 否(需专用工具) |
4.5 什么时候用哪个?
我给你几个场景,你根据情况选:
- 数据量小(< 50MB):用 1.0 JSON。方便调试,方便看数据长啥样。
- 数据量大(> 500MB):强制用 2.0 HDF5。别犹豫,否则你会卡死。
- 元数据复杂:用 2.0。它的分组机制可以存多层结构,1.0 的平铺机制存着存着就乱了。
- 需要跨语言调用:优先 2.0。HDF5 在 C/C++、Python、R、Java 里都有成熟接口。JSON 虽然也有,但遇到大文件就露怯了。
4.6 避坑指南
我曾经踩过一个坑:同一个项目里,课题组用 QIIME 2 出了 2.0 的 biom,我这边用 Python 的 biom-format 包读成 1.0 的 JSON 对象。结果 taxonomy 字段死活解析不对。
后来发现,2.0 里的 taxonomy 是个 变长字符串数组,而 1.0 里是个固定 JSON 数组。转换时数据截断了。
解决办法很简单:
# 如果手里是 2.0 格式,先转成 1.0 再操作
biom convert -i table.biom -o table.json --to-json
# 或者直接用 h5py 读取 2.0 原始数据
import h5py
with h5py.File('table.biom', 'r') as f:
data = f['observation/matrix/data'][:]
print(data.shape)
记住:版本转换时,metadata 的结构会发生变化。尤其是 taxonomy 这类层级信息,一定要检查转换前后的字段是否一致。
4.7 格式选择流程图
下面这张图帮你快速决策,根据你的数据量和元数据复杂度来选择版本:
4.8 转换实战
讲到这里,肯定有人要问:如果手里是 1.0,怎么转 2.0?反过来呢?
工具链很成熟。我用 biom-format 工具包的命令行,基本能解决 99% 的问题。
1.0 JSON → 2.0 HDF5
biom convert \
-i table_1.0.json \
-o table_2.0.biom \
--to-hdf5 \
--table-type="OTU table"
2.0 HDF5 → 1.0 JSON
biom convert \
-i table_2.0.biom \
-o table_1.0.json \
--to-json
注意:从 2.0 转回 1.0 时,某些 metadata 字段可能丢失。因为 1.0 不支持 HDF5 的复杂分组结构。我曾因为 taxonomy 字段里的 lineage 信息被展平,导致后续分析代码报错。所以,转出后一定要检查。
4.9 总结
BIOM 1.0 和 2.0 没有绝对的好坏,只有适不适合。你可以这样记:
- 1.0 JSON:轻量调试神器,适合小项目和教学。
- 2.0 HDF5:生产环境标配,应对大规模数据。
我个人现在写 pipeline,默认全部输出 2.0。只有遇到老旧的上下游模块时,才在中间环节转成 1.0 做桥接。版本选择没有对错,别在格式上纠结太久,核心是把数据存下来、读出来、分析清楚。