MACCS Keys 是一套预定义的 166 个结构问题:「分子中有没有羧酸?」「有没有五元芳香环?」——每个问题的答案就是一位。它的最大特点是每一位都有明确的化学含义。
与 ECFP 的根本差异
| MACCS | ECFP | |
|---|---|---|
| 设计方式 | 人工定义的子结构列表 | 算法枚举 + 哈希 |
| 长度 | 固定 166 位(RDKit 输出 167) | 可配置(常 2048) |
| 可解释性 | 每位有明确含义 | 需要反查子结构 |
| 覆盖面 | 有限——只覆盖预定义的模式 | 覆盖分子中所有子结构 |
| 特异性 | 低 | 高 |
| 计算速度 | 快 | 快 |
| 存储 | 极小 | 较大 |
核心取舍:MACCS 用「覆盖面」换「可解释性与紧凑性」。它无法编码不在列表中的结构特征——而这正是它在预测任务上通常不如 ECFP 的原因。
使用
from rdkit import Chem, DataStructs
from rdkit.Chem import MACCSkeys
import numpy as np
mol = Chem.MolFromSmiles("CC(=O)Nc1ccc(O)cc1")
fp = MACCSkeys.GenMACCSKeys(mol)
print(f"长度: {fp.GetNumBits()}") # 167(第 0 位未使用)
print(f"置位: {fp.GetNumOnBits()}")
print(f"置位的位号: {list(fp.GetOnBits())}")
# 转成数组
arr = np.zeros((167,), dtype=np.int8)
DataStructs.ConvertToNumpyArray(fp, arr)
# ---- 查看每一位对应的 SMARTS ----
from rdkit.Chem.MACCSkeys import smartsPatts
for bit in list(fp.GetOnBits())[:15]:
patt, count = smartsPatts.get(bit, ("?", 0))
print(f"位 {bit:3d}: {patt}")
# 输出示例:
# 位 53: [!#6;!#1]~[CH2]~[!#6;!#1] 杂原子-CH2-杂原子
# 位 84: [NH2] 伯胺
# 位 139: [OH] 羟基
# 位 162: a 芳香原子
# 位 165: [R] 环
#
# 【这个可追溯性是 MACCS 的核心价值】
适用场景
# ---- 场景一:需要可解释的特征 ----
# 建模后想知道「哪些结构特征驱动了预测」
# → MACCS 的特征重要性可以直接解读
import lightgbm as lgb
import numpy as np
from rdkit.Chem.MACCSkeys import smartsPatts
model = lgb.LGBMClassifier(n_estimators=500, verbose=-1)
model.fit(X_maccs, y)
importance = model.feature_importances_
top_bits = np.argsort(importance)[::-1][:15]
print("最重要的结构特征:")
for bit in top_bits:
patt = smartsPatts.get(int(bit), ("未定义",))[0]
print(f" 位 {bit:3d} (重要性 {importance[bit]:5.1f}): {patt}")
# 【比 ECFP 的位号直观得多】
# → 化学家能直接理解
# ---- 场景二:与 ECFP 组合 ----
# 两者编码不同层次的信息
# MACCS: 官能团级别的「有没有」
# ECFP: 精细的局部环境
X_combined = np.hstack([X_maccs, X_ecfp])
# 【常常比单独用任一种略好】
# ---- 场景三:快速的粗筛 ----
# 166 位的计算与比较都很快
# → 超大库的第一道过滤
# ---- 场景四:存储受限 ----
# 166 位 = 21 字节
# 对十亿级的库,存储差异很可观
# ---- 场景五:教学与探索 ----
# 直观理解「指纹在编码什么」
局限与注意事项
- 覆盖面有限:只能编码预定义列表中的模式。新颖的化学结构可能完全无法区分;
- 特异性不足:两个化学上很不同的分子,MACCS 可能非常相似——因为它们碰巧有相同的官能团组合;
- 相似性阈值不同:MACCS 的 Tanimoto 值系统性地高于 ECFP(因为位数少、置位多)——ECFP 的 0.7 阈值不适用,MACCS 通常要用 0.8~0.85;
- 不同实现有差异:RDKit、OpenBabel、CDK 的 MACCS 实现在某些位上略有不同——跨工具比较要注意;
- 第 0 位未使用:RDKit 输出 167 位但第 0 位恒为 0;
- 预测任务上通常不如 ECFP:这是覆盖面的直接后果。
Tanimoto 阈值的差异
# 【实际验证这个差异】
from rdkit import Chem, DataStructs
from rdkit.Chem import MACCSkeys, rdFingerprintGenerator
import numpy as np
gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)
def compare_thresholds(smiles_pairs):
for s1, s2 in smiles_pairs:
m1, m2 = Chem.MolFromSmiles(s1), Chem.MolFromSmiles(s2)
ecfp_sim = DataStructs.TanimotoSimilarity(
gen.GetFingerprint(m1), gen.GetFingerprint(m2))
maccs_sim = DataStructs.TanimotoSimilarity(
MACCSkeys.GenMACCSKeys(m1), MACCSkeys.GenMACCSKeys(m2))
print(f"ECFP {ecfp_sim:.3f} MACCS {maccs_sim:.3f} {s1} / {s2}")
compare_thresholds([
("CC(=O)Nc1ccc(O)cc1", "CC(=O)Nc1ccccc1"),
("c1ccccc1", "c1ccncc1"),
("CCO", "CCCCCCCC"),
])
# 典型结果:
# MACCS 的值系统性地更高
# → 【0.8 的 MACCS 相似度,可能只对应 0.4 的 ECFP 相似度】
#
# 【实践建议】:
# 在自己的数据上校准阈值:
# 取一批已知「相似」与「不相似」的分子对
# 看用什么阈值能最好地区分
# → 不要照搬别处的阈值
选择建议
| 需求 | 建议 |
|---|---|
| 相似性搜索 | ECFP4(见 036《Morgan Fingerprint 入门》) |
| 机器学习(主力特征) | ECFP 计数 + 描述符 |
| 需要可解释的特征重要性 | MACCS |
| 与 ECFP 组合 | 可以,略有提升 |
| 超大库的快速粗筛 | MACCS(快且小) |
| 教学与理解 | MACCS |
| 骨架跃迁 | FCFP 或药效团指纹(见 037《ECFP 指纹详解》) |
关键要点
- 每一位有明确的 SMARTS 定义,可直接解读特征重要性——这是它的核心价值;
- 覆盖面有限:只编码预定义模式,预测任务上通常不如 ECFP;
- MACCS 的 Tanimoto 系统性高于 ECFP——0.7 阈值不适用,通常要 0.8~0.85;
- 不同工具的 MACCS 实现略有差异,跨工具比较要注意。
延伸资源
- 分子指纹总览:035《分子指纹是什么》;ECFP:036《Morgan Fingerprint 入门》、037《ECFP 指纹详解》;
- Tanimoto:039《Tanimoto 相似性》;可解释性:168《可解释性 AI》。