使用 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 秒,就能明白我们到底在干什么。
这张图说清楚了:一个 SMILES 进来,先过 MolFromSmiles 变成分子对象。然后兵分两路——一路算描述符(给你们做机器学习用的特征),一路画结构图(给你做数据分析时用的视觉依据)。最后数据汇到一起,形成完整的特征矩阵加图像存档。
到现在为止,你已经能独立完成从 SMILES 到分子对象的转化、常用描述符的批量计算以及分子结构的可视化输出了。这个技能组合,几乎是所有分子机器学习项目的第一步。嗯,稳扎稳打,继续走。