mcpField
D
drugbank-database-access

drugbank-database-access

github.com/jaechang-hitsデータ・データベース38467 回インストール6 日前に更新
リポジトリ

drugbank-database-access について

drugbank-database-accessは、ローカルに保存したDrugBank XMLをPythonで検索するワークフローです。薬剤や相互作用の検索、標的・化学特性の抽出、外部データベースIDの対応付けに対応します。

主な機能

  1. 01ID・名称・CAS番号による薬剤検索
  2. 02薬剤相互作用の詳細取得
  3. 03標的・酵素・経路の抽出
  4. 04化学特性の取得と類似度計算
  5. 05外部データベースIDとの対応付け

活用シーン

  1. 01服薬リストの相互作用スクリーニング
  2. 02特定のタンパク質標的に関連する薬剤の検索
  3. 03ケモインフォマティクス用の薬剤特性比較
  4. 04DrugBankレコードと外部データベースIDの連結

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/jaechang-hits/sciagent-skills --skill drugbank-database-access

インストール後、Claude Code で/drugbank-database-accessを入力して読み込まれたか確認します。

同梱ファイルreferences/chemical_analysis.mdreferences/interactions_targets.md

詳細

DrugBank データベース — ローカル XML アクセス

概要

ローカルにダウンロードした XML ファイルを Python の ElementTree で解析し、DrugBank の包括的な医薬品データベース(14,000 件以上の医薬品エントリ、5,000 件以上のタンパク質ターゲット、17,000 件以上の医薬品相互作用)を検索します。医薬品検索、相互作用の確認、ターゲット/経路の抽出、化学的特性の分析、データベース間の識別子マッピングに対応しています。

使用する場面

  • DrugBank ID、名前、または CAS 番号で医薬品情報(説明、適応症、作用機序、薬理学)を検索する場合
  • 多剤併用の安全性を確認するため、医薬品間相互作用と重症度分類を調べる場合
  • UniProt アクセッションを含む医薬品ターゲット、酵素、トランスポーター、キャリアを抽出する場合
  • ケモインフォマティクス分析用に化学的特性(SMILES、InChI、分子量)を取得する場合
  • DrugBank エントリを外部データベース(PubChem、ChEMBL、UniProt、KEGG)にマッピングする場合
  • 分子フィンガープリントから医薬品類似性マトリックスを作成する場合
  • ライブの生物活性データ(IC50、Ki、EC50)には、代わりに chembl-database-bioactivity を使用してください
  • データベースをダウンロードせずに化合物の特性を検索するには、代わりに pubchem-compound-search を使用してください

前提条件

  • DrugBank アカウント:https://go.drugbank.com/ で登録します(無料の学術ライセンス)
  • XML のダウンロード:登録後、drugbank_all_full_database.xml.zip をダウンロードします(展開後、約 1.5 GB)
  • Python パッケージ:lxml、rdkit(類似性)、pandas(表形式の分析)
  • REST API(任意):開発者向けプランは月 3,000 リクエストです。一括処理にはローカル XML を使用してください
pip install lxml pandas
pip install rdkit-pypi          # chemical similarity
pip install drugbank-downloader  # programmatic XML download

クイックスタート

import xml.etree.ElementTree as ET

NS = {'db': 'http://www.drugbank.ca'}  # Required for ALL XPath queries

tree = ET.parse('drugbank_all_full_database.xml')  # 30-60s for full XML
root = tree.getroot()

# Build lookup index (DrugBank ID + lowercase name → element)
drug_index = {}
for drug in root.findall('db:drug', NS):
    db_id = drug.find('db:drugbank-id[@primary="true"]', NS)
    name = drug.find('db:name', NS)
    if db_id is not None and name is not None:
        drug_index[db_id.text] = drug
        drug_index[name.text.lower()] = drug

def find_drug(query):
    """Find drug by DrugBank ID, name (case-insensitive), or CAS number."""
    result = drug_index.get(query) or drug_index.get(query.lower())
    if result is not None:
        return result
    for drug in root.findall('db:drug', NS):  # CAS fallback
        cas = drug.find('db:cas-number', NS)
        if cas is not None and cas.text == query:
            return drug
    return None

drug = find_drug('DB00945')  # Aspirin
name = drug.find('db:name', NS).text
print(f"{name}: {drug.find('db:description', NS).text[:100]}...")

コア API

1. データアクセスとセットアップ
import xml.etree.ElementTree as ET

NS = {'db': 'http://www.drugbank.ca'}
tree = ET.parse('drugbank_all_full_database.xml')
root = tree.getroot()
print(f"Total drug entries: {len(root.findall('db:drug', NS))}")

メモリに制約のある環境では、iterparse を使用してください。

drug_names = {}
for event, elem in ET.iterparse('drugbank_all_full_database.xml', events=('end',)):
    if elem.tag == '{http://www.drugbank.ca}drug':
        db_id = elem.find('{http://www.drugbank.ca}drugbank-id[@primary="true"]')
        name = elem.find('{http://www.drugbank.ca}name')
        if db_id is not None and name is not None:
            drug_names[db_id.text] = name.text
        elem.clear()  # Free memory
print(f"Parsed {len(drug_names)} drugs via iterparse")
2. 医薬品情報の検索
def get_drug_info(drug_element):
    """Extract comprehensive drug information."""
    def txt(path):
        el = drug_element.find(path, NS)
        return el.text if el is not None and el.text else None

    return {
        'drugbank_id': txt('db:drugbank-id[@primary="true"]'),
        'name': txt('db:name'),
        'type': drug_element.get('type'),
        'description': txt('db:description'),
        'indication': txt('db:indication'),
        'mechanism_of_action': txt('db:mechanism-of-action'),
        'cas_number': txt('db:cas-number'),
        'groups': [g.text for g in drug_element.findall('db:groups/db:group', NS)],
    }

info = get_drug_info(find_drug('Metformin'))
print(f"{info['name']} ({info['type']}): Groups={info['groups']}")
# Search by name pattern (partial match)
def search_by_name(pattern):
    pattern_lower = pattern.lower()
    return [d for d in root.findall('db:drug', NS)
            if d.find('db:name', NS) is not None
            and pattern_lower in d.find('db:name', NS).text.lower()]

statins = search_by_name('statin')
print(f"Found {len(statins)} drugs matching 'statin'")
3. 医薬品間相互作用
def get_interactions(drug_element):
    """Extract all drug-drug interactions."""
    return [{
        'drugbank_id': i.find('db:drugbank-id', NS).text,
        'name': i.find('db:name', NS).text,
        'description': i.find('db:description', NS).text,
    } for i in drug_element.findall('db:drug-interactions/db:drug-interaction', NS)]

def classify_severity(description):
    """Classify severity from interaction description text."""
    if not description:
        return 'unknown'
    dl = description.lower()
    if any(w in dl for w in ['contraindicated', 'avoid', 'fatal', 'life-threatening']):
        return 'major'
    if any(w in dl for w in ['increase', 'decrease', 'enhance', 'reduce', 'alter']):
        return 'moderate'
    return 'minor'

interactions = get_interactions(find_drug('Aspirin'))
print(f"Aspirin has {len(interactions)} interactions")
for i in interactions[:3]:
    print(f"  [{classify_severity(i['description'])}] {i['name']}")
# Check pairwise interaction between two drugs
def check_interaction(drug1_elem, drug2_elem):
    id2 = drug2_elem.find('db:drugbank-id[@primary="true"]', NS).text
    for inter in get_interactions(drug1_elem):
        if inter['drugbank_id'] == id2:
            inter['severity'] = classify_severity(inter['description'])
            return inter
    return None

result = check_interaction(find_drug('Warfarin'), find_drug('Aspirin'))
if result:
    print(f"[{result['severity']}] {result['description'][:150]}")
4. 医薬品ターゲットと経路
def get_targets(drug_element, target_type='targets'):
    """Extract targets/enzymes/transporters/carriers.
    target_type: 'targets', 'enzymes', 'transporters', or 'carriers'
    """
    results = []
    for target in drug_element.findall(f'db:{target_type}/db:{target_type[:-1]}', NS):
        t = {
            'name': (target.find('db:name', NS).text
                     if target.find('db:name', NS) is not None else None),
            'actions': [a.text for a in target.findall('db:actions/db:action', NS) if a.text],
        }
        poly = target.find('db:polypeptide', NS)
        if poly is not None:
            t['uniprot_id'] = poly.get('id')
            gene = poly.find('db:gene-name', NS)
            t['gene_name'] = gene.text if gene is not None else None
        results.append(t)
    return results

drug = find_drug('Imatinib')
targets = get_targets(drug, 'targets')
enzymes = get_targets(drug, 'enzymes')
print(f"Imatinib — Targets: {len(targets)}, Enzymes: {len(enzymes)}")
for t in targets[:3]:
    print(f"  {t['name']} (UniProt: {t.get('uniprot_id', 'N/A')}) — {t['actions']}")
def get_pathways(drug_element):
    """Extract SMPDB pathway associations."""
    pathways = []
    for pw in drug_element.findall('db:pathways/db:pathway', NS):
        name = pw.find('db:name', NS)
        smpdb = pw.find('db:smpdb-id', NS)
        pathways.append({
            'smpdb_id': smpdb.text if smpdb is not None else None,
            'name': name.text if name is not None else None,
        })
    return pathways

for pw in get_pathways(find_drug('Metformin')):
    print(f"  {pw['smpdb_id']}: {pw['name']}")
5. 化学的特性と類似性
def get_property(drug_element, kind_name, section='calculated'):
    """Get a single property value by kind name."""
    prefix = f'db:{section}-properties/db:property'
    for prop in drug_element.findall(prefix, NS):
        kind = prop.find('db:kind', NS)
        if kind is not None and kind.text == kind_name:
            return prop.find('db:value', NS).text
    return None

def get_all_properties(drug_element):
    """Extract all calculated and experimental properties as a dict."""
    props = {}
    for section in ('calculated', 'experimental'):
        for prop in drug_element.findall(f'db:{section}-properties/db:property', NS):
            kind = prop.find('db:kind', NS)
            value = prop.find('db:value', NS)
            if kind is not None and value is not None:
                key = f'{section}_{kind.text}' if section == 'experimental' else kind.text
                props[key] = value.text
    return props

drug = find_drug('Aspirin')
print(f"SMILES: {get_property(drug, 'SMILES')}")
print(f"MW: {get_property(drug, 'Molecular Weight')}")
print(f"LogP: {get_property(drug, 'logP')}")
# Tanimoto similarity between drugs using RDKit Morgan fingerprints
from rdkit import Chem
from rdkit.Chem import AllChem, DataStructs

def drug_similarity(drug1_elem, drug2_elem, radius=2, nbits=2048):
    smi1, smi2 = get_property(drug1_elem, 'SMILES'), get_property(drug2_elem, 'SMILES')
    if not smi1 or not smi2:
        return None
    mol1, mol2 = Chem.MolFromSmiles(smi1), Chem.MolFromSmiles(smi2)
    if mol1 is None or mol2 is None:
        return None
    fp1 = AllChem.GetMorganFingerprintAsBitVect(mol1, radius, nBits=nbits)
    fp2 = AllChem.GetMorganFingerprintAsBitVect(mol2, radius, nBits=nbits)
    return DataStructs.TanimotoSimilarity(fp1, fp2)

sim = drug_similarity(find_drug('Aspirin'), find_drug('Ibuprofen'))
print(f"Aspirin vs Ibuprofen: {sim:.3f}")
6. データベース間の連携
def get_external_ids(drug_element):
    """Extract all external database identifiers."""
    ids = {}
    for ident in drug_element.findall('db:external-identifiers/db:external-identifier', NS):
        resource = ident.find('db:resource', NS)
        identifier = ident.find('db:identifier', NS)
        if resource is not None and identifier is not None:
            ids[resource.text] = identifier.text
    return ids

ids = get_external_ids(find_drug('Imatinib'))
print(f"PubChem: {ids.get('PubChem Compound')}, ChEMBL: {ids.get('ChEMBL')}, "
      f"KEGG: {ids.get('KEGG Drug')}, UniProt: {ids.get('UniProtKB')}")
# Build cross-reference table for multiple drugs
import pandas as pd

def build_crossref_table(names):
    rows = []
    for name in names:
        d = find_drug(name)
        if d is None: continue
        ids = get_external_ids(d)
        rows.append({'drug': name,
                     'drugbank_id': d.find('db:drugbank-id[@primary="true"]', NS).text,
                     'pubchem': ids.get('PubChem Compound'),
                     'chembl': ids.get('ChEMBL'),
                     'kegg': ids.get('KEGG Drug')})
    return pd.DataFrame(rows)

print(build_crossref_table(['Aspirin', 'Metformin', 'Imatinib', 'Warfarin']).to_string(index=False))

主要な概念

XML 名前空間の処理

DrugBank XML のクエリにはすべて名前空間プレフィックスが必要です。これがないと、XPath は結果を返しません。

NS = {'db': 'http://www.drugbank.ca'}
name = drug.find('db:name', NS).text     # CORRECT
name = drug.find('name')                  # WRONG — returns None!
# For iterparse, use full URI: '{http://www.drugbank.ca}drug'
医薬品エントリの構造
セクションXPath内容
識別情報db:drugbank-id、db:name、db:cas-number主な識別子
薬理学db:description、db:indication、db:mechanism-of-action臨床テキスト、作用機序、PD/PK
相互作用db:drug-interactions/db:drug-interaction説明付きの相互作用する医薬品
ターゲットdb:targets/db:target作用を含むタンパク質ターゲット
酵素/トランスポーター/キャリアdb:enzymes/db:enzyme などCYP450、P-gp、結合タンパク質
経路db:pathways/db:pathwaySMPDB 経路との関連
特性db:calculated-properties、db:experimental-propertiesSMILES、MW、logP など
外部 IDdb:external-identifiersPubChem、ChEMBL、KEGG、UniProt との相互参照
外部識別子のマッピング
XML 内のリソース名データベース例
PubChem CompoundPubChem CID2244
ChEMBLChEMBLCHEMBL25
KEGG Drug / KEGG CompoundKEGGD00109 / C01405
UniProtKBUniProtP23219
PharmGKBPharmGKBPA452615
ChEBIChEBI15365
計算された特性の種類

一般的な kind の値:SMILES、InChI、InChIKey、Molecular Weight、Molecular Formula、logP、logS、Polar Surface Area (PSA)、Rotatable Bond Count、H Bond Acceptor Count、H Bond Donor Count、pKa (strongest acidic)、pKa (strongest basic)、Rule of Five、Bioavailability。

一般的なワークフロー

ワークフロー 1:創薬ターゲットの分析

目標:特定の遺伝子を標的とするすべての医薬品を見つけ、その特性を分析します。

import xml.etree.ElementTree as ET
import pandas as pd

NS = {'db': 'http://www.drugbank.ca'}
tree = ET.parse('drugbank_all_full_database.xml')
root = tree.getroot()

target_gene = 'EGFR'
records = []
for drug in root.findall('db:drug', NS):
    for target in drug.findall('db:targets/db:target', NS):
        poly = target.find('db:polypeptide', NS)
        if poly is None:
            continue
        gene = poly.find('db:gene-name', NS)
        if gene is not None and gene.text == target_gene:
            actions = [a.text for a in target.findall('db:actions/db:action', NS) if a.text]
            records.append({
                'drugbank_id': drug.find('db:drugbank-id[@primary="true"]', NS).text,
                'name': drug.find('db:name', NS).text,
                'groups': ', '.join(g.text for g in drug.findall('db:groups/db:group', NS)),
                'actions': ', '.join(actions),
            })

df = pd.DataFrame(records)
print(f"Drugs targeting {target_gene}: {len(df)}")
print(df.to_string(index=False))
ワークフロー 2:多剤併用の安全性スクリーニング

目標:服用薬のリストについて、すべてのペアごとの相互作用を重症度順にスクリーニングします。

import xml.etree.ElementTree as ET
import pandas as pd

NS = {'db': 'http://www.drugbank.ca'}
tree = ET.parse('drugbank_all_full_database.xml')
root = tree.getroot()

# Build index and interaction maps
idx = {}
inter_map = {}  # drugbank_id → {interacting_id: description}
for drug in root.findall('db:drug', NS):
    name = drug.find('db:name', NS)
    db_id = drug.find('db:drugbank-id[@primary="true"]', NS)
    if name is None or db_id is None:
        continue
    idx[name.text.lower()] = db_id.text
    imap = {}
    for i in drug.findall('db:drug-interactions/db:drug-interaction', NS):
        imap[i.find('db:drugbank-id', NS).text] = i.find('db:description', NS).text
    inter_map[db_id.text] = imap

medications = ['Warfarin', 'Aspirin', 'Omeprazole', 'Atorvastatin', 'Metformin']
report = []
med_ids = [(m, idx.get(m.lower())) for m in medications]
for i, (n1, id1) in enumerate(med_ids):
    if not id1: continue
    for n2, id2 in med_ids[i+1:]:
        if not id2: continue
        desc = inter_map.get(id1, {}).get(id2)
        if desc:
            dl = desc.lower()
            sev = ('MAJOR' if any(w in dl for w in ['contraindicated','avoid','fatal'])
                   else 'MODERATE' if any(w in dl for w in ['increase','decrease','enhance','reduce'])
                   else 'MINOR')
            report.append({'Drug 1': n1, 'Drug 2': n2, 'Severity': sev,
                           'Description': desc[:120]})

df = pd.DataFrame(report)
print(f"=== Polypharmacy Report: {len(medications)} medications, {len(df)} interactions ===")
if not df.empty:
    print(df.sort_values('Severity').to_string(index=False))

主なパラメーター

パラメーター関数/エンドポイントデフォルト説明
NS(名前空間の辞書)すべての XPath クエリ{'db': 'http://www.drugbank.ca'}すべての find/findall 呼び出しで必要です
@primary="true"db:drugbank-id—セカンダリ ID ではなく、プライマリ DrugBank ID(DB00XXX)を選択します
target_typeget_targets()'targets'次のいずれかです:targets、enzymes、transporters、carriers
radiusMorgan フィンガープリント2フィンガープリントの半径です。2 = ECFP4、3 = ECFP6
nbitsMorgan フィンガープリント2048ビットベクトルの長さです。値が大きいほどハッシュ衝突が少なくなります
eventsET.iterparse()—解析イベントです。終了タグで発火させるには ('end',) を使用します

ベストプラクティス

  1. 起動時にメモリー内インデックスを構築する:一度だけ解析し(30~60秒)、ID と小文字の名前をキーにした辞書を構築します。ループ内で再解析しないでください
  2. 名前空間の辞書を必ず渡す:すべての find()/findall() で NS が必要です。これを省略すると、結果が空になる原因の第1位になります
  3. メモリーに制約がある場合は iterparse を使用する:elem.clear() と組み合わせることで、1.5 GB のツリー全体を読み込まずに済みます
  4. None をチェックする:すべての医薬品にすべてのフィールドがあるとは限りません。.text の前に必ず el is not None を確認してください
  5. 実験値より計算値を優先する:計算値(SMILES、logP、MW)は、ほぼすべての医薬品で利用できます
  6. 多剤併用では相互作用マップをキャッシュする:{drug_id: {interacting_id: desc}} を一度だけ事前構築します

よくあるレシピ

レシピ:すべての医薬品プロパティを CSV にエクスポートする
import pandas as pd
records = []
for drug in root.findall('db:drug', NS):
    row = {'drugbank_id': drug.find('db:drugbank-id[@primary="true"]', NS).text,
           'name': drug.find('db:name', NS).text, 'type': drug.get('type')}
    for prop in drug.findall('db:calculated-properties/db:property', NS):
        row[prop.find('db:kind', NS).text] = prop.find('db:value', NS).text
    records.append(row)
pd.DataFrame(records).to_csv('drugbank_properties.csv', index=False)
print(f"Exported {len(records)} drugs")
レシピ:Lipinski の Rule-of-5 フィルター
def check_lipinski(drug_element):
    props = get_all_properties(drug_element)
    try:
        mw = float(props.get('Molecular Weight', 9999))
        logp = float(props.get('logP', 99))
        hba = int(props.get('H Bond Acceptor Count', 99))
        hbd = int(props.get('H Bond Donor Count', 99))
    except (ValueError, TypeError):
        return None
    violations = sum([mw > 500, logp > 5, hba > 10, hbd > 5])
    return {'MW': mw, 'logP': logp, 'HBA': hba, 'HBD': hbd,
            'violations': violations, 'passes': violations <= 1}

print(check_lipinski(find_drug('Imatinib')))
レシピ:CYP450 基質を検索する
cyp = 'CYP3A4'
substrates = []
for drug in root.findall('db:drug', NS):
    for enz in drug.findall('db:enzymes/db:enzyme', NS):
        n = enz.find('db:name', NS)
        if n is not None and n.text and cyp.lower() in n.text.lower():
            actions = [a.text.lower() for a in enz.findall('db:actions/db:action', NS) if a.text]
            if 'substrate' in actions:
                substrates.append(drug.find('db:name', NS).text)
print(f"{cyp} substrates: {len(substrates)}")

トラブルシューティング

問題原因解決策
既知の要素に対して find() が None を返すXML 名前空間が指定されていないfind()/findall() に必ず NS = {'db': 'http://www.drugbank.ca'} を渡してください
XML 全体の解析時に MemoryError が発生するメモリー使用量が約 2~3 GBET.iterparse() と elem.clear() を使用してください
起動が遅い(60秒超)1.5 GB の XML を解析している一度だけ解析してインデックス辞書を構築し、再解析を避けてください
名前で医薬品が見つからない大文字と小文字の区別、または別名小文字に正規化し、CAS または DrugBank ID を試してください
calculated-properties が空バイオテクノロジー医薬品/タンパク質医薬品には SMILES がないdrug.get('type') を確認してください。バイオテクノロジー医薬品には低分子のプロパティがありません
AttributeError: 'NoneType'任意の XML 要素が存在しない.text の前に el is not None を確認してください
相互作用数が対称でないXML 内の相互作用が対称ではない両方向を確認するか、対称なインデックスを構築してください
drugbank-downloader の認証失敗認証情報が無効https://go.drugbank.com/ でアカウントを確認してください
REST API の 429レート制限を超過したバッチクエリではローカル XML に切り替えてください

バンドルされたリソース

references/interactions_targets.md — 相互作用(重大度のヒューリスティクス、一括スクリーニング、説明の解析)とターゲット/経路(ポリペプチドの詳細、アクションカタログ、酵素/トランスポーターの対象範囲、経路エンリッチメント)をまとめています。基本的な抽出(Core API 3-4)はインラインに移動しました。Core API と重複するフィールドごとの詳細な解析は省略しました。

references/chemical_analysis.md — 特性の抽出、記述子の計算、フィンガープリントの類似性、薬物らしさによるフィルタリングを扱います。対象:特性の完全なカタログ、類似性マトリックス、部分構造検索。SMILES/InChI の抽出と Tanimoto(Core API 5)、Lipinski(レシピ)はインラインに移動しました。3D コンフォーマーは省略しました(rdkit-cheminformatics を使用してください)。

元の構成(2,717 行:SKILL.md 190 行 + 参照ファイル 5 件 2,166 行 + スクリプト 351 行):

  • SKILL.md(190) — スタブを 6 つの Core API モジュールで書き換え
  • data-access.md(243)→ Core API 1 + クイックスタート
  • drug-queries.md(387)→ Core API 2
  • interactions.md(426)→ references/interactions_targets.md + Core API 3
  • targets-pathways.md(519)→ references/interactions_targets.md + Core API 4
  • chemical-analysis.md(591)→ references/chemical_analysis.md + Core API 5
  • drugbank_helper.py(351)— 薄いラッパー:find_drug → クイックスタート、get_drug_info/search_by_name → Core API 2、get_interactions/check_interaction/check_polypharmacy → Core API 3、get_targets → Core API 4、get_properties/get_smiles/get_inchi → Core API 5

保持率:SKILL.md は約 550 行です。参照ファイル(約 600 行)を含めると、合計で約 1,150 / 2,717 = 約 42% です。元のスタブはそのままです。参照ファイル 5 件を 2 件に統合(ceil(5/3)=2)。

関連スキル

  • chembl-database-bioactivity — ライブの生物活性データベース(IC50、Ki、EC50)。DrugBank の静的な医薬品カタログを補完します
  • pubchem-compound-search — データベースをダウンロードせずに公開化合物の特性を検索
  • rdkit-cheminformatics — 3D コンフォーマー、高度なフィンガープリント、DrugBank の特性を超える記述子に対応する、包括的なケモインフォマティクスツールキット

参考資料

コメント