任何活性预测模型都始于一份干净的数据。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_type 是 SINGLE PROTEIN。PROTEIN COMPLEX、PROTEIN 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,模型指标好过这个数就该怀疑泄漏。