第4章 分子描述符计算:2D描述符实战
讲ADMET建模,绕不开一个基础问题——怎么把分子结构转成数字。
说白了,计算机看不懂结构式,它只认识数字。分子描述符就是那座桥。今天咱们重点聊2D描述符,特别是那三个最核心的:脂溶性、氢键供/受体、分子量。实战中用RDKit和PaDEL两个工具轮着来。
我个人习惯,做小分子ADMET建模,先用RDKit快速算一轮基础描述符,碰到模型效果不好时,再上PaDEL算个大的。后面会细说为什么这么干。
4.1 你需要了解的描述符基础
2D描述符只依赖分子的拓扑结构,不涉及3D构象。这意味着计算快,可重复性好。你想想看,同一个分子在不同力场下3D构象可能不一样,但2D描述符永远是稳定的。这在工业级建模里特别重要。
先列一下咱们这章要重点算的描述符:
| 描述符 | 缩写 | 意义 | 在ADMET建模中的作用 |
|---|---|---|---|
| 脂溶性 | LogP | 正辛醇/水分配系数的对数值 | 通透性、代谢稳定性、毒性预测 |
| 分子量 | MolWt | 分子质量(Da) | 类药性过滤、Caco-2通透性 |
| 氢键供体数 | HBD | 可提供氢原子的个数 | 血脑屏障渗透、口服生物利用度 |
| 氢键受体数 | HBA | 可接受氢原子的个数 | 与HBD搭配,影响结合自由能 |
| 可旋转键数 | RotBonds | 非环且非末端可旋转键数 | 构象灵活度、结合位点适应性 |
| 拓扑极性表面积 | TPSA | 极性原子贡献的表面积 | 通透性、P-gp底物预测 |
嗯,这里要注意。LogP有计算值和实验值之分。做建模时,我们一般用计算LogP(cLogP或AlogP),因为它对所有分子都有值。实验中测定的LogP虽然准,但很多化合物根本测不了——比如溶解度太低的。
4.2 用RDKit算2D描述符
RDKit是开源界的瑞士军刀。我最早用RDKit做描述符计算时还踩过坑——它默认的LogP模型是哪一种?后来翻文档才知道,RDKit的cLogP是基于atom‑typing的Wildman‑Crippen方法。这个方法对常见骨架挺好的,但对含氟、含硅的奇葩分子就不太准了。
代码很简单:
from rdkit import Chem
from rdkit.Chem import Descriptors, Lipinski
mol = Chem.MolFromSmiles('CC(=O)Nc1ccc(O)cc1')
if mol:
logp = Descriptors.MolLogP(mol)
mw = Descriptors.MolWt(mol)
hbd = Lipinski.NumHDonors(mol)
hba = Lipinski.NumHAcceptors(mol)
rotb = Lipinski.NumRotatableBonds(mol)
tpsa = Descriptors.TPSA(mol)
print(f'LogP: {logp:.2f}')
print(f'MolWt: {mw:.2f}')
print(f'HBD: {hbd} | HBA: {hba}')
print(f'RotBonds: {rotb}')
print(f'TPSA: {tpsa:.2f}')
输出结果:
LogP: 1.10
MolWt: 151.16
HBD: 2
HBA: 2
RotBonds: 1
TPSA: 49.33
我曾经在一次建模项目中,发现模型对含嘧啶环的分子预测偏差很大。查来查去,问题出在RDKit对含氮杂环的LogP计算偏保守。那一次我把数据分成含杂环和不含杂环两组分别建模,效果才上来。这件事让我养成了一个习惯——先算描述符,跑个分布直方图看看,有没有奇怪的离群点。
4.3 PaDEL描述符计算
PaDEL是一个专门的描述符计算工具箱。它支持1D、2D和指纹,总数超过1800个。RDKit算不了的某些分子拓扑特征——比如EState数、AUTOCORR、WHIM描述符——PaDEL全都有。
PaDEL有两种用法,GUI和命令行。做批量计算我推荐命令行。
下载PaDEL-Descriptor.jar后,在终端里跑:
java -jar PaDEL-Descriptor.jar \
-dir ./input_sdf \
-file ./output_descriptors.csv \
-descriptortypes ./PA_2Donly.txt \
-retainorder \
-maxruntime 60000
参数说明:
- -dir:输入的SDF或SMILES文件夹
- -file:输出CSV路径
- -descriptortypes:描述符类型文件,控制只算哪些
- -retainorder:保持输入顺序输出
- -maxruntime:单个分子超时限制(毫秒)
PA_2Donly.txt文件内容示例:
# 2D描述符类型列表
Aromatic atoms count
Autocorrelation (2D)
Barysz matrix
CPSA (2D)
EState
Extended Topochemical Atom (ETA) indices
FMF descriptor
Fragment Complexity
Ghose-Crippen LogP
HBondAcceptorCount
HBondDonorCount
Molecular weight
Number of Rotatable Bonds
Topological polar surface area
VABC index
WeightedPath
XLogP
输出CSV长这样:
Name,nAcid,nBase,nAtom,ALogP,ALogp2,AMR,Apol,Aroma...
CC(=O)Nc1ccc(O)cc1,0,1,11,1.10,1.21,38.45,15.22,6,...
CC(C)Cc1ccc(C(O)=O)cc1,1,0,13,2.85,8.12,47.33,19.64,7,...
这里有个坑——PaDEL默认把所有描述符全算一遍,近2000列。对机器学习来说,维度灾难不是闹着玩的。我曾经有一批1000个分子,用完PaDEL全算完后,特征矩阵5万列。后来模型只跑了5分钟就过拟合了。所以我强烈建议,先通过描述符类型文件过滤,只选你需要的那几类。
4.4 两个工具怎么选?
我一般这样判断:
- 快速原型或小规模数据(< 5000分子):RDKit就够了,代码干净、依赖少、结果好解释。
- 大规模筛选或需要特殊描述符:PaDEL有绝对优势,比如EState指数在预测hERG毒性时就比RDKit的2D描述符效果好。
- 生产流水线:我习惯两个都算。RDKit算基础描述符用于基线模型,PaDEL算扩展描述符用于集成模型。
| 对比维度 | RDKit | PaDEL |
|---|---|---|
| 开源/许可证 | BSD | GPLv2 |
| 描述符总数 | 约200个 | 约1800个 |
| 计算速度 | 快 | 中等 |
| 接口易用性 | Python原生 | 命令行/Java |
| 指纹支持 | 有(ECFP等) | 有(CDK指纹等) |
| 适合场景 | 日常建模、特征选择 | 大规模、细粒度特征工程 |
4.5 实操锦囊:写一个可复用的描述符计算函数
项目做多了你会发现,描述符计算的逻辑在不同任务间高度相似。我习惯封装成函数,放在一个utils.py模块里反复用。
def compute_rdkit_2d(smiles_list, names=None):
"""
批量计算RDKit 2D描述符
"""
from rdkit import Chem, RDLogger
from rdkit.Chem import Descriptors, Lipinski
import pandas as pd
RDLogger.logger().setLevel(RDLogger.ERROR)
results = []
for i, smi in enumerate(smiles_list):
mol = Chem.MolFromSmiles(smi)
if mol is None:
results.append({f'RDKit_{i}': None for i in range(6)})
continue
row = [
Descriptors.MolLogP(mol),
Descriptors.MolWt(mol),
Lipinski.NumHDonors(mol),
Lipinski.NumHAcceptors(mol),
Lipinski.NumRotatableBonds(mol),
Descriptors.TPSA(mol)
]
results.append(row)
cols = ['LogP', 'MolWt', 'HBD', 'HBA', 'RotBonds', 'TPSA']
df = pd.DataFrame(results, columns=cols)
if names is not None:
df.insert(0, 'Name', names)
return df
调用:
smiles = ['CC(=O)Nc1ccc(O)cc1', 'CC(C)Cc1ccc(C(O)=O)cc1']
names = ['paracetamol', 'ibuprofen']
df = compute_rdkit_2d(smiles, names)
print(df)
输出:
Name LogP MolWt HBD HBA RotBonds TPSA
0 paracetamol 1.10 151.16 2 2 1 49.33
1 ibuprofen 2.85 206.28 1 2 3 37.30
你看,两行代码就能把分子转成结构化特征。整章下来就一个核心思路——描述符是建模的起点,起点不对后面全白费。RDKit和PaDEL各有脾性,摸熟了就知道什么时候用谁。
最后给个小提醒:如果你拿到的SMILES有手性信息和同位素标记(比如[13C]),RDKit会无警告地忽略它们,直接按默认同位素算。PaDEL也一样。所以清洗SMILES时,建议先去同位素、去手性,否则同一化合物不同来源的SMILES会算出来不一样的描述符。我就是这么被坑过一次。