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 表示六元环
  • 手性用 @ 或 @@ 表示
我曾经踩过的坑:SMILES 不唯一。同一个分子可以有多种写法。比如乙醇,可以写成 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 无效")
我个人的习惯是:在任何 pipeline 的第一步,先用 RDKit 把所有 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
我这几年做数据整合的经验:在需要合并多个来源的分子数据时,用 InChI 或 InChIKey 做匹配,比用 SMILES 可靠得多。SMILES 容易产生假重复,InChI 基本不会。

用 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 坐标
注意:SDF 文件里的字段名容易弄错。我遇到过 > <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),带出了杂化状态
  • 可以携带形式电荷,用于静电计算
  • 支持子结构定义,适合大分子的分块处理
我的个人建议:如果你的项目涉及分子对接(AutoDock、DOCK 等),MOL2 是必须掌握的格式。平时做 ADMET 数据预处理,我也会优先把分子转成 MOL2 再对接。因为它的原子类型信息比 SDF 丰富得多。

用 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 原子类型丰富、适合对接 格式复杂、软件兼容性一般 分子对接、药效团建模
说穿了:选什么格式取决于下游任务。做统计用 SMILES,做合并用 InChI,做存储用 SDF,做对接用 MOL2。没有万能的格式,只有合适的场景。

我在实际项目中,常常会把这四种格式混合使用。比如:从 CSV 读 SMILES → 转成 InChI 去重 → 用 RDKit 生成 3D 坐标 → 存为 SDF → 再转 MOL2 给对接工具。这个过程用 Snakemake 串起来,就非常优雅了。

4.6 格式转换与数据清洗

最后说一下实际操作。无论你从哪个渠道拿到数据,都会遇到格式不统一的问题。

我建议的预处理流程:

  1. 统一输入格式:不管来源是 CSV、TXT 还是 SDF,先全部转成标准 SMILES
  2. 去重:用 InChIKey 做唯一键,去掉重复分子
  3. 校验:用 RDKit 检查每个分子的有效性,删掉解析失败的
  4. 标准化:统一原子类型、氢原子处理、芳香性表示
  5. 生成目标格式:按下游需求输出 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()
我曾经踩过的坑:直接拿 SDF 文件里的属性值用,没检查类型。比如 IC50 字段可能存的是 "12.5"(字符串)也可能存 12.5(数字)。写解析脚本时一定要加类型判断,不然会挂掉。

这一章的重点就是搞懂这四种格式的脾气。搞懂了它们,你写 Snakemake pipeline 的时候就知道怎么选、怎么转了。

分子格式选择决策流程 输入数据 确定目标格式 SMILES InChI / InChIKey SDF MOL2 表格存储/高效检索 数据库/数据合并 分子库/批处理 分子对接/模拟 选择依据:下游任务 | 数据结构 | 工具兼容性