unichem-database について
UniChemは、公共の化学データベースを横断して調査する研究者向けの、EMBL-EBIによる化学構造クロスリファレンスサービスです。InChIベースの識別子で20以上のデータベースのレコードを対応付け、結合性検索も提供します。
主な機能
- 01InChIKeyによるデータベース横断検索
- 02データソース固有IDによる化合物検索
- 03結合性に基づく類縁化合物の検索
- 04利用可能なデータソース一覧
- 05REST APIによるクロスリファレンス取得
活用シーン
- 01ChEMBL IDをPubChem CIDに変換
- 02複数データベース間で化合物の同一性を確認
- 03立体異性体や塩形のレコードを検索
- 04生物活性テーブルと構造DBの識別子を結合
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ jaechang-hits/ sciagent-skills --skill unichem-databaseインストール後、Claude Code で/unichem-databaseを入力して読み込まれたか確認します。
詳細
UniChem データベース
概要
UniChem は EMBL-EBI が提供する化学構造の相互参照サービスで、InChI ベースの識別子を使って 20 以上の公開化学データベースに登録された化合物レコードを関連付けます。 単一の化学物質を、ChEMBL、DrugBank、PubChem、ChEBI、PDB(RCSB および PDBe)、SureChEMBL、HMDB、DrugCentral、BindingDB などにおける対応する ID に対応付けます。 https://www.ebi.ac.uk/unichem/api/v1/ にある無料の REST API から利用でき、API キーは不要です。 重要:すべての相互参照クエリは JSON 本文を伴う POST で送信します。GET として実装されているのはカタログエンドポイント GET /sources のみです。
使用する場面
- ChEMBL 化合物 ID を PubChem CID、DrugBank アクセッション、または ChEBI ID に変換して、データベース横断分析を行う場合
- InChIKey を使って、ある化合物が登録されているすべてのデータベースソースを特定する場合
- コネクティビティ検索を使い、データベース間で構造的に関連するすべての化合物(同じ結合関係で、立体化学や塩の形態が異なるもの)を見つける場合
- 複数のデータベースのデータセットを統合する前に、ソース間で化合物の同一性を検証する場合
- 創薬プロジェクト向けに化合物の相互参照テーブルを作成する場合(ChEMBL の生物活性データと PDB の構造データを関連付けるなど)
- 合成した化合物やベンダーの化合物が、InChIKey で公開データベースに登録されているか確認する場合
- 完全な生物活性プロファイル(IC50、Ki)には chembl-database-bioactivity を使用してください。UniChem が提供するのは ID の相互参照のみで、実験データは提供しません
- 化合物の特性予測や部分構造検索には pubchem-compound-search を使用してください。UniChem は識別子の変換専用です
前提条件
- Python パッケージ:requests、pandas、matplotlib
- データ要件:化合物の InChIKey(標準的な 27 文字の XXXXXXX… 形式)、ソース固有の ID(例:CHEMBL25)、または開始点となる PubChem CID
- 環境:インターネット接続。API キーは不要です
- レート制限:約 10 リクエスト/秒。バッチループではリクエスト間に time.sleep(0.1) を追加してください。1 日あたりの上限はありません
pip install requests pandas matplotlib
クイックスタート
UniChem の /compounds エンドポイントは POST 専用です。GET は 405 Method Not Allowed を返します。JSON 本文 {type: inchikey, compound: KEY} を送信し、compounds[0][sources] からデータベースごとの該当レコードを読み取ります。各ソースレコードには id(数値のデータベース ID)と compoundId(そのデータベース内の ID)が含まれます。
import requests
UNICHEM_API = "https://www.ebi.ac.uk/unichem/api/v1"
def unichem_post(endpoint: str, body: dict) -> dict:
"""POST request to UniChem API; raise on HTTP errors."""
r = requests.post(f"{UNICHEM_API}/{endpoint}", json=body, timeout=20)
r.raise_for_status()
return r.json()
# Find all database sources for aspirin by InChIKey
inchikey = "BSYNRYMUTXBXSQ-UHFFFAOYSA-N" # aspirin
result = unichem_post("compounds", {"type": "inchikey", "compound": inchikey})
compounds = result.get("compounds", [])
print(f"Found {len(compounds)} compound record(s) for {inchikey}")
if compounds:
sources = compounds[0].get("sources", [])
print(f" Present in {len(sources)} database records")
seen = set()
for src in sources:
if src["id"] in seen:
continue
seen.add(src["id"])
print(f" source id={src['id']:>3} ({src['shortName']:>12}): {src['compoundId']}")
if len(seen) >= 5:
break
# Found 1 compound record(s) for BSYNRYMUTXBXSQ-UHFFFAOYSA-N
# Present in many database records
# source id= 1 ( chembl): CHEMBL25
# source id= 2 ( drugbank): DB00945
# source id= 3 ( rcsb_pdb): AIN
主要 API
クエリ 1:InChIKey の検索 — すべてのソース
標準 InChIKey で化合物を検索し、すべてのデータベースレコードを取得します。これが主要な相互参照方法です。エンドポイントは POST /compounds です。見つかった場合、レスポンスの compounds には 1 件のエントリーが含まれ、各エントリーには sources リストがあります。そのレコードでは id がソースデータベースを、compoundId がそのデータベース内の ID を示します。
import requests, pandas as pd
UNICHEM_API = "https://www.ebi.ac.uk/unichem/api/v1"
# Common source IDs (verify with the /sources endpoint - see Query 4)
SOURCE_NAMES = {
1: "ChEMBL", 2: "DrugBank", 3: "RCSB PDB", 4: "GtoPdb", 5: "PDBe",
7: "ChEBI", 14: "FDA SRS", 15: "SureChEMBL", 18: "HMDB", 22: "PubChem",
31: "BindingDB", 32: "CompTox", 33: "LIPID MAPS", 34: "DrugCentral",
37: "BRENDA", 38: "Rhea", 41: "SwissLipids", 49: "Probes-and-Drugs",
}
def lookup_by_inchikey(inchikey: str) -> pd.DataFrame:
"""Return all database cross-references for an InChIKey."""
r = requests.post(f"{UNICHEM_API}/compounds",
json={"type": "inchikey", "compound": inchikey}, timeout=20)
r.raise_for_status()
compounds = r.json().get("compounds", [])
if not compounds:
return pd.DataFrame()
rows = []
for src in compounds[0].get("sources", []):
rows.append({
"source_id": src["id"],
"source_name": SOURCE_NAMES.get(src["id"], src.get("shortName", "")),
"compound_id": src["compoundId"],
"url": src.get("url", ""),
})
return pd.DataFrame(rows).sort_values(["source_id", "compound_id"])
# Triclosan cross-references
df = lookup_by_inchikey("XEFQLINVKFYRCS-UHFFFAOYSA-N")
print(f"Triclosan found in {df['source_id'].nunique()} distinct databases ({len(df)} records):")
print(df[["source_name", "compound_id"]].head(8).to_string(index=False))
# Triclosan found in 16 distinct databases
# ChEMBL CHEMBL849
# DrugBank DB08604
# RCSB PDB TCL
# ChEBI CHEBI:164200
# Extract specific source IDs from cross-reference table
def get_id_for_source(inchikey: str, source_id: int) -> str | None:
"""Return the compound ID in a specific database, or None if not found."""
r = requests.post(f"{UNICHEM_API}/compounds",
json={"type": "inchikey", "compound": inchikey}, timeout=20)
r.raise_for_status()
compounds = r.json().get("compounds", [])
if not compounds:
return None
for src in compounds[0].get("sources", []):
if src["id"] == source_id:
return src["compoundId"]
return None
triclosan = "XEFQLINVKFYRCS-UHFFFAOYSA-N"
chembl_id = get_id_for_source(triclosan, source_id=1) # ChEMBL
pubchem_id = get_id_for_source(triclosan, source_id=22) # PubChem
drugbank_id = get_id_for_source(triclosan, source_id=2) # DrugBank
print(f"Triclosan: ChEMBL={chembl_id}, PubChem={pubchem_id}, DrugBank={drugbank_id}")
# Triclosan: ChEMBL=CHEMBL849, PubChem=5564, DrugBank=DB08604
クエリ 2:ソース固有 ID による化合物検索
特定のソースデータベースにおける既知の化合物 ID(例:ChEMBL ID)から、すべての相互参照を取得します。本文に type: sourceID と化合物のソース ID、および数値の sourceID を指定します。InChIKey 検索と同じデータ構造が返されます。
import requests
UNICHEM_API = "https://www.ebi.ac.uk/unichem/api/v1"
def get_sources_for_compound(compound_id: str, source_id: int) -> list:
"""Get all database cross-references for a compound identified in a specific source.
Args:
compound_id: The ID in the source database (e.g., CHEMBL192)
source_id: UniChem source ID (1=ChEMBL, 2=DrugBank, 22=PubChem, 7=ChEBI)
"""
body = {"type": "sourceID", "compound": compound_id, "sourceID": source_id}
r = requests.post(f"{UNICHEM_API}/compounds", json=body, timeout=20)
r.raise_for_status()
compounds = r.json().get("compounds", [])
if not compounds:
return []
return compounds[0].get("sources", [])
# Sildenafil (Viagra): look up starting from ChEMBL ID
sources = get_sources_for_compound("CHEMBL192", source_id=1)
distinct_dbs = {s["id"] for s in sources}
print(f"Sildenafil (CHEMBL192): {len(sources)} source records across {len(distinct_dbs)} databases")
seen = set()
for s in sources:
if s["id"] in seen:
continue
seen.add(s["id"])
print(f" [{s['id']:>3}] {s['shortName']:>15}: {s['compoundId']}")
if len(seen) >= 8:
break
# Sildenafil (CHEMBL192): 272 source records across 19 databases
クエリ 3:コネクティビティ検索 — 構造的に関連する化合物
同じ基本構造を持ちながら、立体化学、塩の形態、同位体標識、またはプロトン化状態が異なる化合物を見つけます。エンドポイントは POST /connectivity で、標準形式の完全な InChIKey を受け付けます(API は 14 文字の断片を拒否し、404 Not found を返します)。UniChem は内部的にコネクティビティ層を使って照合しますが、立体化学、電荷、同位体の各層が異なる該当レコードも返します。
/compounds とは異なり、コネクティビティのレスポンスはフラットな sources リスト(関連する全化合物にまたがるデータベースレコードを 1 件ずつ格納)に加えて、searchedCompound および totalCompounds/totalSources の概要フィールドを返します。各レコードの comparison 辞書には、どの InChI 層が一致したかが示されます。
import requests, pandas as pd
UNICHEM_API = "https://www.ebi.ac.uk/unichem/api/v1"
def connectivity_search(inchikey: str) -> dict:
"""Find all compounds related by InChI connectivity (same skeleton, possibly different stereo/salts).
Pass a FULL standard InChIKey (27 chars). The server rejects 14-char fragments.
"""
body = {"type": "inchikey", "compound": inchikey}
r = requests.post(f"{UNICHEM_API}/connectivity", json=body, timeout=30)
r.raise_for_status()
return r.json()
# Warfarin: find all stereoforms, racemates, and salt forms
warfarin_inchikey = "PJVWKTKQMONHTI-UHFFFAOYSA-N"
data = connectivity_search(warfarin_inchikey)
print(f"Warfarin connectivity relatives: {data['totalCompounds']} unique compounds, "
f"{data['totalSources']} database records")
hits = data.get("sources", [])
by_source = {}
for h in hits:
by_source.setdefault(h["shortName"], []).append(h["compoundId"])
for name, ids in sorted(by_source.items(), key=lambda kv: -len(kv[1]))[:8]:
print(f" {name:>15}: {len(ids):>4} IDs (e.g. {ids[0]})")
# Warfarin connectivity relatives: 13 unique compounds, 353 database records
# Compare source coverage across connectivity relatives
def compare_coverage(inchikey: str) -> pd.DataFrame:
"""Show connectivity relatives split by their source-database coverage."""
data = connectivity_search(inchikey)
rows = []
for src in data.get("sources", []):
rows.append({
"source_id": src["id"],
"source_name": src["shortName"],
"compound_id": src["compoundId"],
"stereo_match": src["comparison"].get("stereoSp3", False),
"salt_match": src["comparison"].get("protonation", False),
})
return pd.DataFrame(rows)
df = compare_coverage("PJVWKTKQMONHTI-UHFFFAOYSA-N")
print(f"Total records: {len(df)}")
print(df.head(10).to_string(index=False))
print(f"Records with stereo mismatch (skeleton matches, stereo differs): {(~df['stereo_match']).sum()}")
クエリ 4:すべてのデータソースを一覧表示
ID、名前、説明、ウェブサイト URL を含む UniChem の全データソース一覧を取得します。GET で提供されるのはこのエンドポイントのみです。レスポンスでは、各ソースエントリー内で sourceID(大文字の ID)が使われます。
import requests, pandas as pd
UNICHEM_API = "https://www.ebi.ac.uk/unichem/api/v1"
def list_sources() -> pd.DataFrame:
"""Return all UniChem data sources as a DataFrame."""
r = requests.get(f"{UNICHEM_API}/sources", timeout=15)
r.raise_for_status()
sources = r.json().get("sources", [])
rows = []
for s in sources:
rows.append({
"source_id": s["sourceID"],
"name": s.get("nameLong") or s.get("nameLabel", ""),
"label": s.get("nameLabel", ""),
"short_name": s.get("name", ""),
"uci_count": s.get("UCICount"),
"url": s.get("baseIdUrl", ""),
})
return pd.DataFrame(rows).sort_values("source_id")
sources_df = list_sources()
print(f"Total UniChem sources: {len(sources_df)}")
print(sources_df[["source_id", "label", "uci_count"]].to_string(index=False))
# Total UniChem sources: 23
# 1 ChEMBL 2854815
# 2 DrugBank 14622
# 22 PubChem 123392679
クエリ 5:化合物ごとのループ(バッチエンドポイントなし)
UniChem はリストをまとめて送信する形式をサポートしていません。/compounds の POST で受け付けられるのは、1 リクエストにつき 1 つの化合物のみです。{compounds: [...]} または {inchikeys: [...]} を送信すると、400 illegal_argument_exception が返されます。複数の入力を処理する場合は、約 10 req/s のレート制限を守るため、少し待機を挟みながらループで処理してください。
import requests, time, pandas as pd
UNICHEM_API = "https://www.ebi.ac.uk/unichem/api/v1"
def batch_translate(inchikeys: list[str],
target_source_ids=(1, 2, 7, 22)) -> pd.DataFrame:
"""Translate a list of InChIKeys to IDs in multiple target databases.
Loops one POST per InChIKey (UniChem has no list-batch endpoint).
"""
SOURCE_NAMES = {1: "chembl", 2: "drugbank", 3: "pdb", 7: "chebi",
14: "fda_srs", 22: "pubchem", 34: "drugcentral"}
rows = []
for ik in inchikeys:
row = {"inchikey": ik}
for sid in target_source_ids:
row[SOURCE_NAMES.get(sid, f"src_{sid}")] = None
try:
r = requests.post(f"{UNICHEM_API}/compounds",
json={"type": "inchikey", "compound": ik}, timeout=20)
r.raise_for_status()
compounds = r.json().get("compounds", [])
if compounds:
for src in compounds[0].get("sources", []):
if src["id"] in target_source_ids:
col = SOURCE_NAMES.get(src["id"], f"src_{src['id']}")
if row[col] is None:
row[col] = src["compoundId"]
except requests.RequestException as e:
row["error"] = str(e)
rows.append(row)
time.sleep(0.1) # respect ~10 req/s rate limit
return pd.DataFrame(rows)
# Translate a set of NSAIDs by InChIKey
nsaid_inchikeys = [
"BSYNRYMUTXBXSQ-UHFFFAOYSA-N", # aspirin
"HEFNNWSXXWATRW-UHFFFAOYSA-N", # ibuprofen
"CMWTZPSULFXXJA-VIFPVBQESA-N", # naproxen
"DCOPUUMXTXDBNB-UHFFFAOYSA-N", # diclofenac (free acid)
]
df = batch_translate(nsaid_inchikeys, target_source_ids=[1, 2, 7, 22])
print(df.to_string(index=False))
df.to_csv("nsaid_xrefs.csv", index=False)
print(f"Saved nsaid_xrefs.csv ({len(df)} compounds)")
クエリ 6:ソース ID を入力する化合物ごとのループ
開始時の識別子が InChIKey ではなく、ソース固有の ID(例:バイオアクティビティ表にある ChEMBL ID のリスト)の場合は、type=sourceID を使用し、ID ごとに 1 回の POST を行うループで処理してください。
import requests, time
UNICHEM_API = "https://www.ebi.ac.uk/unichem/api/v1"
def translate_source_ids(ids: list[str], from_source: int,
to_sources=(2, 22, 34)) -> list[dict]:
"""Translate IDs in one source database to IDs in target databases.
Args:
ids: list of compound IDs in the from_source database
from_source: source ID of the input list (1=ChEMBL, 22=PubChem, ...)
to_sources: iterable of target source IDs to extract
"""
out = []
for cid in ids:
body = {"type": "sourceID", "compound": cid, "sourceID": from_source}
r = requests.post(f"{UNICHEM_API}/compounds", json=body, timeout=20)
row = {"input": cid}
if r.ok:
compounds = r.json().get("compounds", [])
if compounds:
row["inchikey"] = compounds[0].get("standardInchiKey")
hits = {s["id"]: s["compoundId"] for s in compounds[0].get("sources", [])}
for tsid in to_sources:
row[f"src_{tsid}"] = hits.get(tsid)
out.append(row)
time.sleep(0.1)
return out
# Three kinase inhibitors known by ChEMBL ID
chembl_inputs = ["CHEMBL535", "CHEMBL553", "CHEMBL941"] # nilotinib, dasatinib, imatinib
rows = translate_source_ids(chembl_inputs, from_source=1, to_sources=(2, 22, 34))
for row in rows:
ik = (row.get("inchikey") or "?")[:14]
print(f"{row['input']:>10} ik={ik}... "
f"DrugBank={row.get('src_2')}, PubChem={row.get('src_22')}, "
f"DrugCentral={row.get('src_34')}")
主な概念
InChI と InChIKey
UniChem は、標準的な化合物識別子として InChI(IUPAC International Chemical Identifier)と、そのハッシュ形式である InChIKey を使用します。InChIKey は 27 文字の文字列で、3 つのブロックに分かれています。最初の 14 文字は結合層(重原子と結合)を表し、次の 8 文字は立体化学と電荷を表し、最後の文字はバージョンフラグです。UniChem は標準 InChIKey が一致することを条件に化合物を相互参照し、データベース間で同じ化学種を確実に特定します。
ソース ID 参照表(/sources とライブ照合済み)
| ソース ID | データベース | 対象範囲 |
|---|---|---|
| 1 | ChEMBL | 生物活性分子、創薬 |
| 2 | DrugBank | 承認薬、薬理学 |
| 3 | RCSB PDB | 結晶構造中のリガンド(米国) |
| 4 | Guide to Pharmacology | 薬理学の標的/リガンド |
| 5 | PDBe | 結晶構造中のリガンド(欧州) |
| 7 | ChEBI | 化学オントロジー、代謝物 |
| 14 | FDA SRS | FDA Substance Registration System |
| 15 | SureChEMBL | 特許化学 |
| 18 | HMDB | Human Metabolome Database |
| 22 | PubChem | 一般的な化合物リポジトリ |
| 31 | BindingDB | 結合親和性データ |
| 32 | CompTox | 環境毒性ダッシュボード |
| 33 | LIPID MAPS | 脂質構造 |
| 34 | DrugCentral | 承認薬と薬理学 |
| 37 | BRENDA | 酵素基質/生成物 |
| 38 | Rhea | 生化学反応 |
| 41 | SwissLipids | 脂質構造 |
| 49 | Probes-and-Drugs | 化学プローブ |
フィールド名:id と sourceID
UniChem のスクリプト作成で最もよくある間違いです。2 つのエンドポイントでは、ソースデータベース識別子に異なるフィールド名を使用します。
- GET /sources は、sourceID フィールド(ID の I が大文字)のオブジェクトとしてデータベースを一覧表示します。
- POST /compounds と POST /connectivity は、データベースごとのヒットを sources リスト内に返します。この場合、ソース識別子は通常の id フィールドです。これらのヒットレコードには sourceID または sourceId キーはありません。
化合物または接続性のレスポンスを反復処理する場合は常に src["id"] を使用し、/sources カタログを反復処理する場合は s["sourceID"] を使用してください。
接続性と標準 InChIKey の照合
POST /compounds は InChIKey が完全一致するもの(立体化学、塩、同位体が同一)を返します。POST /connectivity は結合トポロジーを共有するすべての化合物を返します。これは、ラセミ体、立体異性体、遊離酸/塩基、共結晶の相手を見つけるのに役立ちます。接続性のレスポンスには各ヒットの比較 dict が含まれ、一致した InChI 層(stereoSp3、protonation、isotope など)が示されます。これを使って、骨格が同じで立体化学のみが異なる類縁体に絞り込めます。
一般的なワークフロー
ワークフロー 1:医薬品化合物の相互参照レポート
目標:医薬品名(または ChEMBL ID)のリストを受け取り、それぞれを主要なすべてのデータベース ID に対応付けて CSV にエクスポートします。
import requests, time, pandas as pd
UNICHEM_API = "https://www.ebi.ac.uk/unichem/api/v1"
CHEMBL_API = "https://www.ebi.ac.uk/chembl/api/data"
SOURCE_NAMES = {1: "chembl", 2: "drugbank", 3: "rcsb_pdb",
7: "chebi", 22: "pubchem", 14: "fda_srs", 34: "drugcentral"}
def chembl_to_inchikey(chembl_id: str) -> str | None:
"""Look up the standard InChIKey for a ChEMBL compound ID."""
r = requests.get(f"{CHEMBL_API}/molecule/{chembl_id}.json", timeout=15)
if r.status_code == 404:
return None
r.raise_for_status()
return r.json().get("molecule_structures", {}).get("standard_inchi_key")
def inchikey_to_sources(inchikey: str) -> dict:
"""Return source_id -> compound_id dict for an InChIKey (first hit per source)."""
r = requests.post(f"{UNICHEM_API}/compounds",
json={"type": "inchikey", "compound": inchikey}, timeout=20)
r.raise_for_status()
compounds = r.json().get("compounds", [])
if not compounds:
return {}
out = {}
for s in compounds[0].get("sources", []):
out.setdefault(s["id"], s["compoundId"])
return out
# Example: top cardiovascular drugs
drug_chembl_ids = {
"atorvastatin": "CHEMBL1487",
"lisinopril": "CHEMBL1237",
"metoprolol": "CHEMBL13",
"amlodipine": "CHEMBL1491",
"warfarin": "CHEMBL1464",
}
rows = []
for name, chembl_id in drug_chembl_ids.items():
ik = chembl_to_inchikey(chembl_id)
row = {"drug": name, "chembl_id": chembl_id, "inchikey": ik}
if ik:
srcs = inchikey_to_sources(ik)
for sid, col in SOURCE_NAMES.items():
row[col] = srcs.get(sid)
rows.append(row)
time.sleep(0.2)
df = pd.DataFrame(rows)
df.to_csv("drug_xrefs.csv", index=False)
print(df[["drug", "chembl", "drugbank", "pubchem", "chebi"]].to_string(index=False))
print(f"Saved drug_xrefs.csv ({len(df)} drugs)")
ワークフロー 2:構造類縁体の発見と可視化
目標:化合物の構造類縁体をすべて見つけ、データベースでの収録状況を要約し、ソースごとの分布を示す棒グラフを作成します。
import requests, pandas as pd
import matplotlib.pyplot as plt
from collections import Counter
UNICHEM_API = "https://www.ebi.ac.uk/unichem/api/v1"
SOURCE_NAMES = {1: "ChEMBL", 2: "DrugBank", 3: "RCSB PDB", 5: "PDBe",
7: "ChEBI", 14: "FDA SRS", 15: "SureChEMBL",
22: "PubChem", 31: "BindingDB", 34: "DrugCentral"}
# Aspirin connectivity relatives (covers acetylsalicylate salts)
query_inchikey = "BSYNRYMUTXBXSQ-UHFFFAOYSA-N"
r = requests.post(f"{UNICHEM_API}/connectivity",
json={"type": "inchikey", "compound": query_inchikey}, timeout=30)
r.raise_for_status()
data = r.json()
hits = data.get("sources", [])
print(f"Aspirin connectivity relatives: {data['totalCompounds']} unique compounds, "
f"{len(hits)} database records")
# Count how often each named database appears
source_counter = Counter()
for h in hits:
if h["id"] in SOURCE_NAMES:
source_counter[SOURCE_NAMES[h["id"]]] += 1
labels = [k for k, _ in source_counter.most_common()]
counts = [v for _, v in source_counter.most_common()]
fig, ax = plt.subplots(figsize=(9, 4))
bars = ax.bar(labels, counts, color="#2E86AB", edgecolor="white")
ax.bar_label(bars, padding=2)
ax.set_xlabel("Database")
ax.set_ylabel("Number of Source Records (relatives x hits)")
ax.set_title("UniChem Connectivity Records - Aspirin Skeleton")
plt.xticks(rotation=30, ha="right")
plt.tight_layout()
plt.savefig("unichem_connectivity_coverage.png", dpi=150, bbox_inches="tight")
print("Saved unichem_connectivity_coverage.png")
plt.close(fig)
# DataFrame summary by source
df = pd.DataFrame(source_counter.most_common(), columns=["database", "records"])
print(df.to_string(index=False))
ワークフロー 3:ChEMBL バイオアクティビティと PubChem CID の統合
目標:ChEMBL のバイオアクティビティ表に PubChem CID を追加し、PubChem 識別子を使用するツールで後続の解析を行えるようにします。
import requests, time, pandas as pd
UNICHEM_API = "https://www.ebi.ac.uk/unichem/api/v1"
def add_pubchem_cids(df: pd.DataFrame,
inchikey_col: str = "standard_inchi_key") -> pd.DataFrame:
"""Add pubchem_cid column to a DataFrame that has an InChIKey column."""
unique_keys = df[inchikey_col].dropna().unique()
mapping = {}
for ik in unique_keys:
try:
r = requests.post(f"{UNICHEM_API}/compounds",
json={"type": "inchikey", "compound": ik}, timeout=15)
r.raise_for_status()
compounds = r.json().get("compounds", [])
if compounds:
for src in compounds[0].get("sources", []):
if src["id"] == 22: # PubChem
mapping[ik] = src["compoundId"]
break
except requests.RequestException:
pass
time.sleep(0.1)
df = df.copy()
df["pubchem_cid"] = df[inchikey_col].map(mapping)
return df
# Simulate a small ChEMBL activity table
chembl_data = pd.DataFrame({
"compound_name": ["aspirin", "ibuprofen", "naproxen"],
"standard_inchi_key": [
"BSYNRYMUTXBXSQ-UHFFFAOYSA-N",
"HEFNNWSXXWATRW-UHFFFAOYSA-N",
"CMWTZPSULFXXJA-VIFPVBQESA-N",
],
"ic50_nm": [2500.0, 13000.0, 1600.0],
})
enriched = add_pubchem_cids(chembl_data)
print(enriched[["compound_name", "ic50_nm", "pubchem_cid"]].to_string(index=False))
enriched.to_csv("chembl_with_pubchem.csv", index=False)
print("Saved chembl_with_pubchem.csv")
主要なパラメーター
| パラメーター | エンドポイント | デフォルト | 範囲/オプション | 影響 |
|---|---|---|---|---|
| type(本文) | POST /compounds, POST /connectivity | - | inchikey, sourceID | compound 内の識別子の種類を選択します。inchikey には標準 InChIKey(27 文字)が必要です。sourceID の場合は、数値の sourceID フィールドも必要です。 |
| compound(本文) | POST /compounds, POST /connectivity | - | 文字列 1 つ(リスト不可) | 検索する識別子です。UniChem はリストを使ったバッチ形式をサポートしていないため、化合物ごとに POST を 1 回実行します。 |
| sourceID(本文) | POST /compounds(type=sourceID の場合) | - | /sources の整数 DB ID | 入力した化合物が属するソースデータベースです(1=ChEMBL、2=DrugBank、22=PubChem、...)。 |
| ヒットごとの id(レスポンス) | POST /compounds, POST /connectivity | - | 整数 | sources 内の各ヒットのソースデータベース ID です。ヒットごとのこれらのレコードでは、src["sourceID"] ではなく src["id"] を使用してください。 |
| sourceID(レスポンス) | GET /sources | - | 整数 | カタログ内の各データベースエントリーの数値 ID(大文字の ID)です。 |
| timeout | すべてのリクエスト | 20s | 正の整数 | リクエストが失敗するまでの秒数です。一般的な骨格に対する /connectivity では、30s に引き上げてください。 |
ベストプラクティス
-
/sources 以外ではすべて POST を使用する:GET /compounds は 405 Method Not Allowed を返します。GET /compounds/{src}/{id} と GET /connectivity/{key} は 404 を返します。GET エンドポイントは /sources のみです。必ず JSON 本文を指定して POST してください。
-
正しいフィールドを使用する—ヒットでは id、/sources では sourceID:POST /compounds と POST /connectivity が返す sources リストでは、各レコードのソースデータベースは id キーに格納されています。src["sourceID"] を読み込むと KeyError が発生します。大文字の ID を持つ sourceID フィールドは、GET /sources が返すカタログにのみ存在します。
-
標準の InChIKey を使用する(非標準のものは使用しない):UniChem は標準 InChIKey をインデックス化しています。非標準の InChIKey では結果が返されません。次の方法で確認できます:from rdkit.Chem.inchi import MolToInchiKey; MolToInchiKey(mol)。
-
完全一致しない場合はコネクティビティ検索に切り替える:化合物が DrugBank では塩(塩酸塩など)として登録されていて、手元にあるのが遊離塩基の InChIKey の場合、標準検索では見つかりません。医薬品の相互参照を行う際は、フォールバックとしてコネクティビティ検索を実行してください。
-
完全な InChIKey を /connectivity に渡す:このエンドポイントには、標準 InChIKey 全体(27 文字)が必要です。コネクティビティ部分の 14 文字だけを送信すると、{response: Not found} が返されます。UniChem は内部で立体化学/電荷レイヤーを取り除きます。
-
起動時にソースリストをキャッシュする:/sources を 1 回呼び出し、ID をハードコーディングするのではなく、{sourceID: nameLabel} の辞書を作成します。
def load_source_map() -> dict:
r = requests.get(f"{UNICHEM_API}/sources", timeout=15)
r.raise_for_status()
return {s["sourceID"]: s.get("nameLabel", str(s["sourceID"]))
for s in r.json().get("sources", [])}
- バッチエンドポイントはありません—sleep を挟んでループする:{compounds: [...]} または {inchikeys: [...]} を送信すると、400 illegal_argument_exception が返されます。呼び出しの間に time.sleep(0.1) を挟んで、POST を繰り返します(約 10 リクエスト/秒)。
よく使うレシピ
レシピ:任意の化合物 ID を InChIKey に変換する
使用する場面:PubChem CID または ChEBI ID があり、UniChem やその他のサービスで検索するための InChIKey が必要な場合です。
import requests
UNICHEM_API = "https://www.ebi.ac.uk/unichem/api/v1"
PUBCHEM_API = "https://pubchem.ncbi.nlm.nih.gov/rest/pug"
def pubchem_cid_to_inchikey(cid) -> str | None:
"""Resolve a PubChem CID to a standard InChIKey via PubChem."""
r = requests.get(f"{PUBCHEM_API}/compound/cid/{cid}/property/InChIKey/JSON", timeout=10)
if r.status_code == 404:
return None
r.raise_for_status()
props = r.json()["PropertyTable"]["Properties"]
return props[0]["InChIKey"] if props else None
def cid_to_all_sources(cid) -> list:
"""PubChem CID -> InChIKey -> UniChem cross-references."""
ik = pubchem_cid_to_inchikey(cid)
if not ik:
return []
r = requests.post(f"{UNICHEM_API}/compounds",
json={"type": "inchikey", "compound": ik}, timeout=20)
r.raise_for_status()
compounds = r.json().get("compounds", [])
return compounds[0].get("sources", []) if compounds else []
sources = cid_to_all_sources(2244) # PubChem CID for aspirin
distinct = {s["id"] for s in sources}
print(f"Aspirin (CID=2244) is in {len(distinct)} UniChem databases ({len(sources)} records)")
seen = set()
for s in sources:
if s["id"] in seen:
continue
seen.add(s["id"])
print(f" [{s['id']:>3}] {s['shortName']:>15}: {s['compoundId']}")
if len(seen) >= 6:
break
レシピ:化合物が承認済み医薬品かどうかを確認する
使用する場面:InChIKey を使って、化合物が DrugBank(ソース 2)に登録されているかどうかをすばやく確認する場合です。
import requests
UNICHEM_API = "https://www.ebi.ac.uk/unichem/api/v1"
def is_approved_drug(inchikey: str) -> tuple[bool, str | None]:
"""Check if compound appears in DrugBank (id=2). Returns (is_drug, DrugBank_ID)."""
r = requests.post(f"{UNICHEM_API}/compounds",
json={"type": "inchikey", "compound": inchikey}, timeout=20)
r.raise_for_status()
compounds = r.json().get("compounds", [])
if not compounds:
return False, None
for src in compounds[0].get("sources", []):
if src["id"] == 2: # DrugBank
return True, src["compoundId"]
return False, None
# Test a few compounds
test_compounds = {
"aspirin": "BSYNRYMUTXBXSQ-UHFFFAOYSA-N",
"triclosan": "XEFQLINVKFYRCS-UHFFFAOYSA-N",
"sildenafil": "BNRNXUUZRGQAQC-UHFFFAOYSA-N",
}
for name, ik in test_compounds.items():
is_drug, db_id = is_approved_drug(ik)
status = f"DrugBank:{db_id}" if is_drug else "not in DrugBank"
print(f"{name:>12}: {status}")
レシピ:化合物セットのソース網羅状況を要約する
使用する場面:化合物リストをどのデータベースがカバーしているかを監査する場合です。後続の分析に使用するデータベースを選ぶ前に役立ちます。
import requests, time, pandas as pd
UNICHEM_API = "https://www.ebi.ac.uk/unichem/api/v1"
SOURCE_NAMES = {1: "ChEMBL", 2: "DrugBank", 3: "RCSB PDB", 7: "ChEBI",
14: "FDA SRS", 22: "PubChem", 34: "DrugCentral"}
def source_coverage_matrix(inchikeys: list[str]) -> pd.DataFrame:
"""Return a boolean matrix: rows=compounds, columns=databases."""
rows = []
for ik in inchikeys:
r = requests.post(f"{UNICHEM_API}/compounds",
json={"type": "inchikey", "compound": ik}, timeout=15)
row = {"inchikey": ik}
for sid, name in SOURCE_NAMES.items():
row[name] = False
if r.ok:
compounds = r.json().get("compounds", [])
if compounds:
ids_present = {s["id"] for s in compounds[0].get("sources", [])}
for sid, name in SOURCE_NAMES.items():
row[name] = sid in ids_present
rows.append(row)
time.sleep(0.1)
return pd.DataFrame(rows)
sample_keys = [
"BSYNRYMUTXBXSQ-UHFFFAOYSA-N", # aspirin
"HEFNNWSXXWATRW-UHFFFAOYSA-N", # ibuprofen
"XEFQLINVKFYRCS-UHFFFAOYSA-N", # triclosan
]
coverage = source_coverage_matrix(sample_keys)
print(coverage.to_string(index=False))
print("Coverage per database:")
for col in list(SOURCE_NAMES.values()):
print(f" {col}: {coverage[col].sum()}/{len(coverage)}")
トラブルシューティング
| 問題 | 原因 | 解決策 |
|---|---|---|
| /compounds で 405 Method Not Allowed が発生する | GET でエンドポイントにアクセスしている | UniChem の /compounds は POST 専用です。requests.post(url, json={type:inchikey, compound:ik}) でリクエストを送信してください。 |
| レコードごとの処理で KeyError: sourceID(または sourceId)が発生する | フィールド名を誤って読み取っている | compounds[0][sources] 内のヒットと、/connectivity の sources リストでは src["id"] を使用してください。sourceID は /sources カタログにのみ存在します。 |
| 400 illegal_argument_exception field name is null or empty が発生する | {compounds: [...]} または {inchikeys: [...]} で一括処理を試みた | 一括処理用のエンドポイントはありません。POST リクエストの間に time.sleep(0.1) を挟んで繰り返してください。 |
| /connectivity から {response: Not found} が返される | 14 文字のフラグメントだけを渡した | 27 文字の完全な標準 InChIKey を送信してください。UniChem は内部で立体化学レイヤーを除去します。 |
| 既知の化合物なのに compounds リストが空になる | 標準外の InChIKey、塩形の不一致、または化合物が存在しない | RDKit で InChIKey を確認してください。塩形や立体化学のバリエーションを確認するには、POST /connectivity を試してください。 |
| connectivity からレコードが多すぎる | 同じ骨格が多くの SureChEMBL 特許 ID に一致している | src["id"] でフィルタリングしてソース 15(SureChEMBL)を除外するか、comparison.stereoSp3 == True で絞り込んでください。 |
| requests.exceptions.Timeout が発生する | 負荷時に API の応答が遅い | /connectivity のタイムアウトを 30s に延長し、指数バックオフを使って一度再試行してください。 |
| ソース URL フィールドが空である | URL テンプレートを提供していないソースがある | /sources エンドポイントの baseIdUrl と compoundId を組み合わせて、手動でリンクを作成してください。 |
関連スキル
- chembl-database-bioactivity - UniChem で解決した化合物 ID を使用して、ChEMBL から生物活性データ(IC50、Ki)を照会します
- pubchem-compound-search - UniChem から取得した PubChem CID を使用して、化合物のプロパティとバイオアッセイを詳細に照会します
- pdb-database - UniChem で解決した PDB リガンドコード(ソース 3 または 5)を使用して、3D リガンド構造を検索します
- drugbank-database-access - UniChem から取得した DrugBank ID を使用して、薬理学、ADMET、薬物相互作用の詳細データを取得します
参考資料
- UniChem API ドキュメント - すべてのエンドポイントの説明を含む公式 REST API リファレンス
- UniChem ホームページ - インタラクティブな検索インターフェースとデータベース一覧
- Chambers et al., J Cheminform 2013 - InChI ベースの相互参照手法について説明した UniChem の原著論文
- InChI Trust - InChI 標準仕様とアルゴリズムのドキュメント