Bemis–Murcko 骨架是化学信息学中提取分子核心结构的标准方法。它的规则简单明确——正因如此,它成为了骨架划分、多样性分析、SAR 组织的通用基础。
算法规则
# 【核心规则】:
# 保留:
# 1) 所有的环系
# 2) 连接环系的链(linker)
# 去掉:
# 3) 所有末端的取代基(不在环与环之间路径上的部分)
#
# 【具体做法】:
# 反复删除「度为 1 且不在环中」的原子,
# 直到没有可删的为止
#
# 【例子】:
# CC(=O)Nc1ccc(OC(C)C)cc1
# → 去掉乙酰基与异丙基
# → 骨架:c1ccc(N)cc1 的相关形式
#
# 【注意】:
# 连接两个环的链会被保留
# c1ccccc1CCc1ccccc1 → 骨架包含中间的 CC
from rdkit import Chem
from rdkit.Chem.Scaffolds import MurckoScaffold
examples = [
"CC(=O)Nc1ccc(O)cc1", # 对乙酰氨基酚
"CCCCCCCC", # 无环 → 空骨架
"c1ccccc1CCc1ccccc1", # 两环 + 连接链
"CC(C)Cc1ccc(cc1)C(C)C(=O)O", # 布洛芬
]
for smi in examples:
mol = Chem.MolFromSmiles(smi)
scaf = MurckoScaffold.MurckoScaffoldSmiles(mol=mol)
print(f"{smi:35s} → {scaf if scaf else '【空】'}")
三个变体
| 变体 | 保留什么 | 用途 |
|---|---|---|
| 标准 Murcko | 环 + 连接链,保留原子类型与键级 | 最常用 |
| Generic(图框架) | 只保留拓扑,所有原子变碳、键变单键 | 更宽松的分组 |
| Ring system | 只保留独立的环系,去掉连接链 | 环系多样性分析 |
mol = Chem.MolFromSmiles("c1ccc(cc1)C(=O)Nc1ccncc1")
# 标准
scaf = MurckoScaffold.GetScaffoldForMol(mol)
print("标准:", Chem.MolToSmiles(scaf))
# Generic
generic = MurckoScaffold.MakeScaffoldGeneric(scaf)
print("Generic:", Chem.MolToSmiles(generic))
# 【苯环与吡啶环变成同一个 C1CCCCC1】
# 【选择依据】:
# 标准骨架 → 严格区分杂环类型
# 适合:SAR 分析、专利检索
# Generic → 只看拓扑形状
# 适合:
# - 【更严格的数据划分】(见 051)
# 因为把苯与吡啶归为一类,划分更严
# - 拓扑多样性评估
# - 【判断是否真的发生了骨架跃迁】(见 352)
# 只换杂原子不算真正的跃迁
边界情况
# 【情况一:无环分子】
mol = Chem.MolFromSmiles("CCCCCCCC")
print(repr(MurckoScaffold.MurckoScaffoldSmiles(mol=mol))) # ''
#
# 【后果】:
# 所有无环分子被归为同一「骨架」(空字符串)
# → 骨架划分时它们会全部落在同一边
# → 【如果数据集中无环分子多,需要单独处理】
#
# 【应对】:
def safe_scaffold(smiles):
mol = Chem.MolFromSmiles(smiles)
if mol is None:
return None
scaf = MurckoScaffold.MurckoScaffoldSmiles(mol=mol)
if not scaf:
# 无环分子:用规范 SMILES 本身作为「骨架」
return f"acyclic:{Chem.MolToSmiles(mol)}"
return scaf
# 【情况二:骨架仍然很大】
# 多环分子的骨架可能仍然很复杂
# → 每个分子几乎有自己的骨架
# → 骨架划分退化成随机划分
# 【检查】:骨架数 / 分子数 的比例
# 【情况三:骨架过于笼统】
# 很多不同的分子共享「苯环」这个骨架
# → 划分不够严格
# 【应对】:用 generic 或聚类划分(见 050)
# 【情况四:立体化学】
# 默认保留手性 → 对映体被视为不同骨架
scaf_no_chiral = MurckoScaffold.MurckoScaffoldSmiles(
mol=mol, includeChirality=False)
# 【骨架划分时通常应该去掉手性】
# 【情况五:多组分(盐)】
# 骨架提取前应该先去盐(见 047)
from rdkit.Chem.MolStandardize import rdMolStandardize
mol = rdMolStandardize.FragmentParent(mol)
骨架的层次化组织
# 【Scaffold Tree 的思想】:
# 从一个复杂骨架出发,
# 逐步移除环,形成一棵层次树
# → 可以在不同的抽象层次上分组分子
#
# 简化的实现思路:
from rdkit import Chem
from rdkit.Chem.Scaffolds import MurckoScaffold
def scaffold_hierarchy(smiles):
"""生成骨架的多个抽象层次"""
mol = Chem.MolFromSmiles(smiles)
if mol is None:
return {}
levels = {"molecule": Chem.MolToSmiles(mol)}
scaf = MurckoScaffold.GetScaffoldForMol(mol)
levels["murcko"] = Chem.MolToSmiles(scaf)
try:
levels["generic"] = Chem.MolToSmiles(
MurckoScaffold.MakeScaffoldGeneric(scaf))
except Exception:
levels["generic"] = None
# 环系数量与大小
ri = scaf.GetRingInfo()
levels["n_rings"] = ri.NumRings()
levels["ring_sizes"] = sorted(len(r) for r in ri.AtomRings())
return levels
# 【用途】:
# 在不同层次上分析多样性:
# - 分子层面:完全不同的分子有多少?
# - Murcko 层面:不同的骨架有多少?
# - Generic 层面:【不同的拓扑有多少?】
# → 【generic 层面的多样性才是真正的骨架多样性】
在数据划分中的应用
# 【骨架划分的标准实现】(见 051)
from collections import defaultdict
def scaffold_split(smiles_list, frac_train=0.8, frac_valid=0.1,
generic=False, balanced=True):
"""按骨架划分数据集"""
groups = defaultdict(list)
for i, smi in enumerate(smiles_list):
mol = Chem.MolFromSmiles(smi)
if mol is None:
continue
scaf = MurckoScaffold.GetScaffoldForMol(mol)
if generic:
try:
scaf = MurckoScaffold.MakeScaffoldGeneric(scaf)
except Exception:
pass
key = Chem.MolToSmiles(scaf) or f"acyclic_{i}"
groups[key].append(i)
sets = list(groups.values())
if balanced:
# 【大骨架优先进训练集】—— 让测试集包含更多罕见骨架
sets = sorted(sets, key=len, reverse=True)
n = len(smiles_list)
n_train, n_valid = frac_train * n, frac_valid * n
train, valid, test = [], [], []
for s in sets:
if len(train) + len(s) <= n_train:
train += s
elif len(valid) + len(s) <= n_valid:
valid += s
else:
test += s
return train, valid, test
# 【验证划分是否有效】:
# 计算测试集与训练集之间的最大 Tanimoto 相似度分布
# → 如果很多测试分子的最大相似度 > 0.7,
# 说明骨架划分【不够严格】(见 167)
# → 考虑用 generic 骨架或聚类划分
与其它分组方式的比较
| 方式 | 严格程度 | 特点 |
|---|---|---|
| 随机划分 | 最松 | 不对应任何真实场景 |
| Murcko 骨架划分 | 中 | 标准做法 |
| Generic 骨架划分 | 较严 | 把杂环变体归为一类 |
| 指纹聚类划分 | 严 | 整体相似的分子分在同一边(见 050《分子相似性聚类》) |
| 时间划分 | 最接近真实 | 需要时间戳(见 051《Scaffold Split》) |
关键要点
- 算法是反复删除「度为 1 且不在环中」的原子,保留环系与连接链;
- 无环分子的骨架是空字符串,需要单独处理否则会全部落在划分的同一边;
- Generic 骨架把苯环与吡啶归为一类,用于更严格的划分与判断真正的骨架跃迁;
- 骨架划分后应检查测试集与训练集的最大相似度分布,确认是否足够严格。
延伸资源
- 骨架概念:040《Scaffold 骨架》;Scaffold Split:051《Scaffold Split》;R 基团分解:042《R-group Decomposition》;
- 聚类:050《分子相似性聚类》;骨架跃迁:352《Scaffold Hopping 实战》;模型外推性:167《模型外推性》。