4. 分子表示与标准格式:SMILES、InChI、SDF、MOL2 文件解析
做 ADMET 数据处理,首先得搞清楚你手里拿的是什么格式的数据。
说实话,我刚开始接触这行的时候也踩过坑——拿到的分子文件五花八门,有的看起来像乱码,有的打开全是二进制。后来才发现,搞懂几种主流格式,是预处理的第一步,也是最关键的一步。
这章我们聊四种最常用的分子表示法:SMILES、InChI、SDF 和 MOL2。每种都有它的脾气,我在项目里都吃过它们的亏,今天一并说清楚。
4.1 SMILES:最火的线性表示法
SMILES 全称是 Simplified Molecular Input Line Entry System。说白了,就是用一行字符串表示一个分子。
比如苯,SMILES 是这样的:
c1ccccc1
水的 SMILES 更简单:
O
你看,一个字母就代表了整个分子。SMILES 的优点是紧凑、易读、方便存储。一个 CSV 文件里放 SMILES 列,比放完整的分子结构节省太多空间。
SMILES 的规则要点:
- 原子用元素符号表示,有机元素(C、N、O、P、S、卤素)可省略方括号,其他元素需写完整(例如 [Fe])
- 单键用
-,双键用=,三键用# - 分支用括号
()包裹 - 环通过数字标记,比如
c1ccccc1表示六元环 - 手性用
@或@@表示
CCO,也可以写成 OCC。看起来一样,但对程序来说可能不同。所以做数据合并时一定要先标准化(canonicalize)。我建议用 RDKit 统一转成规范 SMILES。
举个解析例子,用 Python 读取 SMILES:
from rdkit import Chem
smiles = "CCO"
mol = Chem.MolFromSmiles(smiles)
if mol:
print("分子有效:", Chem.MolToSmiles(mol))
else:
print("SMILES 无效")
4.2 InChI:为了标准化而生
为什么有了 SMILES 还要 InChI?
因为 SMILES 不唯一。你写一个,我写一个,谁敢保证长得一样?InChI(International Chemical Identifier)就是来解决这个问题的——它为每个分子生成唯一的、结构化的标识符。
看看 InChI 长什么样:
InChI=1S/C2H6O/c1-2-3/h3H,2H2,1H3
这是乙醇的 InChI。注意这个格式:1S 表示版本号,然后是分子式 C2H6O,后面跟着连接表和氢原子信息。
InChI 的优点:
- 唯一性高,适合作为分子索引
- 可逆,从 InChI 能还原出分子结构
- 有层级结构,不同级别的信息可以选择性去掉
InChIKey:更短的哈希版本,固定 27 个字符,适合用作数据库主键。
例如乙醇的 InChIKey:
LFQSCWFLJHTTHZ-UHFFFAOYSA-N
用 RDKit 互转的例子:
from rdkit import Chem
smiles = "CCO"
mol = Chem.MolFromSmiles(smiles)
inchi = Chem.MolToInchi(mol)
inchikey = Chem.MolToInchiKey(mol)
print("InChI:", inchi)
print("InChIKey:", inchikey)
4.3 SDF:带属性的分子存储格式
SMILES 和 InChI 都是线性字符串,能存结构,但存不了其他信息——比如活性数据、分子量、logP。这时就需要 SDF(Structure Data File)。
SDF 是一种表格式的分子文件。一条记录包含:
- 分子结构(用 MOL 块描述)
- 数据字段(以
> <字段名>分隔) - 以
$$$$结束
一个典型 SDF 记录长这样:
ChemDraw01071922332D
34 35 0 0 0 0 0 0 0 0999 V2000
5.1730 -1.2510 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
4.9230 -0.4210 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
...
M END
> <IC50>
12.5
> <MW>
180.16
$$$$
SDF 的用途:
- 批量存储分子库,一个文件包含多个分子
- 每个分子可携带任意数量的自定义属性
- 支持 2D/3D 坐标
> <LogP> 和 > <logP> 混着写的情况,解析出来全是 NaN。所以写代码时一定要统一字段名大小写,最好全部转小写再处理。
Python 解析 SDF:
from rdkit import Chem
supplier = Chem.SDMolSupplier("data.sdf")
for mol in supplier:
if mol is None:
continue
print("分子: ", Chem.MolToSmiles(mol))
# 获取属性
ic50 = mol.GetProp("IC50") if mol.HasProp("IC50") else None
print("IC50: ", ic50)
4.4 MOL2:来自 Sybyl 的精细格式
MOL2 是 Tripos 公司的 Sybyl 软件使用的分子格式。它的结构比 SDF 的 MOL 块更详细,特别适合分子对接和药效团建模。
MOL2 格式按区块组织:
@<TRIPOS>MOLECULE:分子基本信息(名称、原子数、键数等)@<TRIPOS>ATOM:原子坐标、原子类型、电荷@<TRIPOS>BOND:键连接信息- 可选:
SUBSTRUCTURE、CRYSIN等
举个例子:
@<TRIPOS>MOLECULE
ethanol
9 8 1 0 0
SMALL
USER_CHARGES
@<TRIPOS>ATOM
1 C1 1.234 0.000 0.000 C.3 1 ETHANOL -0.10
2 C2 2.567 0.000 0.000 C.3 1 ETHANOL -0.05
3 O1 3.100 1.200 0.000 O.3 1 ETHANOL -0.35
4 H1 0.800 -0.500 0.800 H 1 ETHANOL 0.10
...
@<TRIPOS>BOND
1 1 2 1
2 2 3 1
3 1 4 1
...
MOL2 的特殊之处:
- 定义了 Sybyl 原子类型(如 C.3、O.3、N.ar),带出了杂化状态
- 可以携带形式电荷,用于静电计算
- 支持子结构定义,适合大分子的分块处理
用 Python 读取 MOL2:
from rdkit import Chem
mol = Chem.MolFromMol2File("ethanol.mol2")
if mol:
print("MOL2 解析成功")
print("SMILES:", Chem.MolToSmiles(mol))
else:
print("解析失败")
如果需要批量处理:
mols = Chem.MolFromMol2File("ligands.mol2", sanitize=True, removeHs=False)
# RdKit 读取多条目 MOL2 需用 SDMolSupplier 配合 Mol2 文件
# 或者自行逐条读取
4.5 四种格式,怎么选?
| 格式 | 优点 | 缺点 | 我的使用场景 |
|---|---|---|---|
| SMILES | 紧凑、易存、适合表格 | 不唯一、无坐标、易出错 | CSV/Excel 数据,大规模筛选 |
| InChI | 唯一、标准化 | 字符串长、不直观 | 数据库索引、跨源合并 |
| SDF | 带属性、带坐标、可多记录 | 文件大、字段易乱 | 分子库存储、预测结果保存 |
| MOL2 | 原子类型丰富、适合对接 | 格式复杂、软件兼容性一般 | 分子对接、药效团建模 |
我在实际项目中,常常会把这四种格式混合使用。比如:从 CSV 读 SMILES → 转成 InChI 去重 → 用 RDKit 生成 3D 坐标 → 存为 SDF → 再转 MOL2 给对接工具。这个过程用 Snakemake 串起来,就非常优雅了。
4.6 格式转换与数据清洗
最后说一下实际操作。无论你从哪个渠道拿到数据,都会遇到格式不统一的问题。
我建议的预处理流程:
- 统一输入格式:不管来源是 CSV、TXT 还是 SDF,先全部转成标准 SMILES
- 去重:用 InChIKey 做唯一键,去掉重复分子
- 校验:用 RDKit 检查每个分子的有效性,删掉解析失败的
- 标准化:统一原子类型、氢原子处理、芳香性表示
- 生成目标格式:按下游需求输出 SDF 或 MOL2
举个例子,批量转换 SMILES 到 SDF:
from rdkit import Chem
from rdkit.Chem import Descriptors
smiles_list = ["CCO", "c1ccccc1", "CC(=O)O"]
mols = []
for smi in smiles_list:
mol = Chem.MolFromSmiles(smi)
if mol:
mol.SetProp("MW", str(Descriptors.MolWt(mol)))
mols.append(mol)
writer = Chem.SDWriter("output.sdf")
for mol in mols:
writer.write(mol)
writer.close()
IC50 字段可能存的是 "12.5"(字符串)也可能存 12.5(数字)。写解析脚本时一定要加类型判断,不然会挂掉。
这一章的重点就是搞懂这四种格式的脾气。搞懂了它们,你写 Snakemake pipeline 的时候就知道怎么选、怎么转了。