使用 RDKit 解析分子:读取 SMILES、计算描述符、可视化

好,我们正式开始动手玩分子数据了。前面几章我们聊了 MoleculeNet 是啥、数据长什么样,但说白了,那些 CSV 里的 SMILES 字符串只是一串文本,计算机不知道它代表一个苯环还是一个蛋白质片段。这章我们就用 RDKit 把这个文本变成真正的“分子”——能算性质、能画结构、能参与建模的那种。

本章核心路径:SMILES 字符串 → RDKit 分子对象 → 分子描述符 → 可视化图形

4.1 从 SMILES 到 RDKit 分子对象

我个人偏好把 RDKit 称为“分子界的瑞士军刀”。安装很简单,用 pip 或者 conda 都行。

pip install rdkit-pypi

拿到一个 SMILES,怎么变成分子?看下面这几行,这是我每次写新脚本时必敲的第一段代码。

from rdkit import Chem

smiles = "CCO"  # 乙醇的 SMILES
mol = Chem.MolFromSmiles(smiles)

if mol is not None:
    print(f"分子原子数: {mol.GetNumAtoms()}")
    print(f"分子式: {Chem.rdMolDescriptors.CalcMolFormula(mol)}")
else:
    print("SMILES 解析失败")

MolFromSmiles 这个函数,看起来简单,但我在项目里踩过不少坑。比如输入的 SMILES 里混了个空格,或者包含 RDKit 不认识的特殊符号,返回值就是 None。所以我一直建议:解析后务必做空值检查,不然你在 None 对象上调用方法,程序直接就崩了。

还有一种情况需要注意——SMILES 可能看起来很正规,但分子没有正确加氢。RDKit 默认以“隐含氢”模式处理,你看到的原子数不是完整的。如果你需要显式氢(比如做力场计算),要手动加一下。

mol = Chem.AddHs(mol)
print(f"含氢原子数: {mol.GetNumAtoms()}")

注意:某些操作要求在加氢前做,某些在加氢后做。比如 SanitizeMol(验证分子价态)通常默认在 MolFromSmiles 时自动执行。如果你手动 AddHs 之后再 SanitizeMol,你可能会发现多出的氢被当作非法连接。嗯,这个顺序我记了好几次才记牢。

4.2 计算分子描述符:把化学变成数字

分子描述符,说白了就是把分子的结构、组成、性质量化成数值。这些数值才是机器学习的输入。RDKit 提供了几百种描述符,从最简单的原子数、分子量,到复杂的拓扑极性表面积、LogP,应有尽有。

我建议你从最常用的几个开始。

描述符名称 含义 RDKit 函数
MolWt 分子量 Descriptors.MolWt(mol)
NumHDonors 氢键供体数 Descriptors.NumHDonors(mol)
NumHAcceptors 氢键受体数 Descriptors.NumHAcceptors(mol)
TPSA 拓扑极性表面积 Descriptors.TPSA(mol)
MolLogP 油水分配系数 Descriptors.MolLogP(mol)

下面是一段批量计算的示例。我用过一个 MoleculeNet 的子集,里面有上千个分子,挨个算描述符,写起来就是这种感觉。

from rdkit.Chem import Descriptors

smiles_list = ["CCO", "c1ccccc1", "CC(=O)O"]
descriptor_names = ["MolWt", "NumHDonors", "NumHAcceptors", "TPSA"]

results = []
for s in smiles_list:
    mol = Chem.MolFromSmiles(s)
    if mol is None:
        continue
    # 计算并收集描述符
    row = [s]
    for name in descriptor_names:
        func = getattr(Descriptors, name)
        row.append(func(mol))
    results.append(row)

# 打印结果
for r in results:
    print(r)

getattr 这个写法,让我可以动态调用描述符函数,不用一个一个写死。你想想看,如果要从 200 个描述符里挑 50 个,手动调用会写死人。所以我一直用这种反射式的写法。

小提示:有些描述符计算很慢(比如 Chi 系列、Kappa 系列),如果你只是做快速筛选,建议先只算基础描述符,等建模阶段再扩特征集。

4.3 分子可视化:看一眼比读一百行代码管用

算完描述符,你看到的还是数字。但有时候,一个分子的二维结构图能直接告诉你很多信息——比如官能团的位置、分子的对称性、有没有异常的长链。RDKit 的画图能力非常强,而且能直接嵌入 Jupyter Notebook。

最基本的可视化就两行代码。

from rdkit.Chem import Draw

mol = Chem.MolFromSmiles("CC(=O)Oc1ccccc1C(=O)O")  # 阿司匹林
img = Draw.MolToImage(mol)
img.show()

如果你一次想看多个分子,可以用 MolToGridImage。

smiles_list = ["CCO", "c1ccccc1", "CC(=O)O", "C1=CC=CC=C1"]
mols = [Chem.MolFromSmiles(s) for s in smiles_list]
# 过滤掉 None
mols = [m for m in mols if m is not None]

grid = Draw.MolsToGridImage(mols, molsPerRow=2, subImgSize=(300, 300))
grid.show()

我个人很依赖可视化来做数据质量检查。还记得有一次,我从 MoleculeNet 的 Tox21 里读了一批分子,算 LogP 时发现某个分子值异常大——画出来一看,原来 SMILES 写成了长碳链,但末端缺了一个环。那个错误要是没画图看,光靠数值根本发现不了。

我曾经踩过的坑:MolsToGridImage 在分子数量很大时很容易导致 Jupyter 内核崩溃。后来我学乖了,超过 50 个分子我会分批画,或者先保存成图片文件,而不是直接渲染在 Notebook 里。

4.4 本章知识体系速览

下面这张 SVG 把本章的核心流程串起来了,你盯着它看 10 秒,就能明白我们到底在干什么。

RDKit 解析分子核心流程 SMILES 字符串 MolFromSmiles() RDKit Mol 对象 计算分子描述符 MolToImage() 可视化 特征矩阵 / 结构图像 SMILES → 解析 → 分子对象 → 同步计算描述符 与 绘制结构图 → 最终产出数字特征和图像

这张图说清楚了:一个 SMILES 进来,先过 MolFromSmiles 变成分子对象。然后兵分两路——一路算描述符(给你们做机器学习用的特征),一路画结构图(给你做数据分析时用的视觉依据)。最后数据汇到一起,形成完整的特征矩阵加图像存档。

到现在为止,你已经能独立完成从 SMILES 到分子对象的转化、常用描述符的批量计算以及分子结构的可视化输出了。这个技能组合,几乎是所有分子机器学习项目的第一步。嗯,稳扎稳打,继续走。