326

从 ChEMBL 拉取靶点活性数据:AI 建模第一步

从 ChEMBL 拉靶点活性数据是 AI 建模的第一步。这篇给出完整可运行的取数脚本、必须加的过滤条件,以及重复记录的处理方式。

任何活性预测模型都始于一份干净的数据。ChEMBL(见 226《ChEMBL》)是最常用的来源,但原始查询结果绝不能直接拿去训练——过滤条件写不对,模型学到的就是噪声。这篇给出一套可直接运行的取数流程。

准备

pip install chembl_webresource_client pandas rdkit

小规模查询(几千条以内)用 Web 客户端;正经建模建议下载本地 SQLite 库(约 4 GB),Web API 拉几万条会非常慢且容易超时。

第一步:找到正确的靶点 ID

from chembl_webresource_client.new_client import new_client
import pandas as pd

target_api = new_client.target
res = target_api.search("EGFR")

for t in res[:10]:
    print(t["target_chembl_id"], "|", t["organism"], "|",
          t["target_type"], "|", t["pref_name"])

# 选择:organism = Homo sapiens,target_type = SINGLE PROTEIN
# EGFR 人源单蛋白 = CHEMBL203

务必确认 target_typeSINGLE PROTEINPROTEIN COMPLEXPROTEIN FAMILY 这类条目的活性数据归属含糊,混进来会污染数据集。

第二步:拉取活性数据(带完整过滤)

activity_api = new_client.activity

records = activity_api.filter(
    target_chembl_id="CHEMBL203",
    standard_type__in=["IC50"],       # 一次只取一种类型
    assay_type="B",                   # B=binding, F=functional,不要混
    standard_relation="=",            # 排除 > < 这类删失数据
    pchembl_value__isnull=False,       # 只要已统一换算的 pChEMBL
).only([
    "molecule_chembl_id", "canonical_smiles",
    "standard_type", "standard_value", "standard_units",
    "standard_relation", "pchembl_value",
    "assay_chembl_id", "assay_description",
    "document_chembl_id", "target_organism",
])

df = pd.DataFrame(records)
print(f"原始记录:{len(df)} 条")
过滤条件 为什么必须加
standard_relation="=" 「IC50 > 10000 nM」是删失数据,当精确值会严重污染训练集
pchembl_value__isnull=False ChEMBL 已统一换算成 −log10(M),避免自己处理混杂单位出错
assay_type="B" 结合与功能测定测的是不同的量,数值不可混用
单一 standard_type IC50、Ki、Kd 含义不同,混训是常见错误

第三步:按测定质量过滤

Web 客户端拿不到 confidence_score,需要另查 assay 表:

assay_api = new_client.assay
assay_ids = df["assay_chembl_id"].dropna().unique().tolist()

conf = {}
for i in range(0, len(assay_ids), 200):          # 分批查,避免 URL 过长
    chunk = assay_ids[i:i+200]
    for a in assay_api.filter(assay_chembl_id__in=chunk).only(
            ["assay_chembl_id", "confidence_score"]):
        conf[a["assay_chembl_id"]] = a["confidence_score"]

df["confidence_score"] = df["assay_chembl_id"].map(conf)
df = df[df["confidence_score"] >= 8]             # 8/9 = 直接分配到单一蛋白
print(f"高置信度记录:{len(df)} 条")

confidence_score 是 0–9 的分数,表示「活性与靶点的对应关系有多确定」。建模建议只用 ≥ 8——低分记录的靶点归属很不可靠。

第四步:处理重复测量

同一化合物-靶点常有多条来自不同文献的记录。处理方式直接影响数据质量

df["pchembl_value"] = pd.to_numeric(df["pchembl_value"], errors="coerce")
df = df.dropna(subset=["pchembl_value", "canonical_smiles"])

grp = df.groupby("molecule_chembl_id")["pchembl_value"]
stats = grp.agg(n="size", median="median", spread=lambda x: x.max() - x.min())

# 关键:跨度超过 1 个 log 单位的化合物,数据本身有问题,整条剔除
reliable = stats[(stats["n"] == 1) | (stats["spread"] <= 1.0)]
print(f"剔除矛盾记录 {len(stats) - len(reliable)} 个化合物")

smiles_map = df.drop_duplicates("molecule_chembl_id").set_index(
    "molecule_chembl_id")["canonical_smiles"]

final = pd.DataFrame({
    "chembl_id": reliable.index,
    "smiles": smiles_map.loc[reliable.index].values,
    "pIC50": reliable["median"].values,      # 中位数比均值稳健
    "n_measurements": reliable["n"].values,
})
print(f"最终数据集:{len(final)} 个化合物")
print(final["pIC50"].describe())
final.to_csv("egfr_ic50_raw.csv", index=False)

第五步:基本质检

import matplotlib.pyplot as plt

# 1) 活性分布是否合理(应大致连续,不应有奇怪的堆积)
print(final["pIC50"].describe())

# 2) 是否有大量相同值(提示数据录入问题)
print(final["pIC50"].value_counts().head())

# 3) 数据量是否够
# < 100  → 建模意义有限
# 100~500 → 只能做粗排序,需谨慎
# > 1000 → 可以认真建模

# 4) 活性范围是否够宽
print("活性跨度:", final["pIC50"].max() - final["pIC50"].min(), "log 单位")
# 跨度 < 2 个 log 单位时,模型难以学到有意义的 SAR

要记住的噪声水平

公开活性数据本身有实验误差:同一化合物-靶点在不同文献间的 pIC50 标准差通常在 0.5 个 log 单位左右。这意味着:

  • 模型 RMSE 到 0.5~0.7 就接近数据噪声天花板了,再优化收益有限;
  • 报告 RMSE 为 0.3 的模型,很可能存在数据泄漏(同分子跨划分出现)。

常见坑与提示

  • 四个必加过滤:relation='='pchembl_value 非空、单一 assay 类型、confidence_score ≥ 8
  • 重复测量取中位数;跨度 > 1 log 的化合物整条剔除;
  • 务必记录 ChEMBL 版本号,否则结果无法复现;
  • 数据噪声约 0.5 log,模型指标好过这个数就该怀疑泄漏。

延伸资源