038

MACCS Keys:经典结构指纹在药物筛选中的用途

MACCS Keys 是 166 位的可解释指纹。这篇讲清它的构成、适用场景与相对 ECFP 的取舍。

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 实现略有差异,跨工具比较要注意。

延伸资源