Claude Skill

alterlab-bindingdb

Query BindingDB for measured protein-ligand binding affinities (Ki, Kd, IC50, EC50) via its keyless REST API or the full TSV download, searching by target (UniProt ID), compound (SMILES), or pathogen. Use when looking up experimental binding constants, profiling inhibitors of a p

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download alterlab-ieu-alterlab-academic-skills-skills_databases_alterlab-bindingdb-e4836c0.zip · 11 KB
Part of alterlab-ieu/alterlab-academic-skills — 94 skills

Install

skills CLI npx skills add https://github.com/AlterLab-IEU/AlterLab-Academic-Skills/tree/main/skills/databases/alterlab-bindingdb
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install alterlab-ieu-alterlab-academic-skills@llmmart
Git git clone https://github.com/AlterLab-IEU/AlterLab-Academic-Skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole alterlab-ieu/alterlab-academic-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

BindingDB Database

Overview

BindingDB (https://www.bindingdb.org/) is the primary public database of measured drug-protein binding affinities. It contains roughly 3.2 million binding data records for ~1.4 million compounds tested against ~11,500 protein targets (homepage figures, 2026-09), curated from scientific literature and patent literature. BindingDB stores quantitative binding measurements (Ki, Kd, IC50, EC50) essential for drug discovery, pharmacology, and computational chemistry research.

Key resources:

When to Use This Skill

Use BindingDB when:

  • Target-based drug discovery: What known compounds bind to a target protein? What are their affinities?
  • SAR analysis: How do structural modifications affect binding affinity for a series of analogs?
  • Lead compound profiling: What targets does a compound bind (selectivity/polypharmacology)?
  • Benchmark datasets: Obtain curated protein-ligand affinity data for ML model training
  • Repurposing analysis: Does an approved drug bind to an unintended target?
  • Competitive analysis: What is the best reported affinity for a target class?
  • Fragment screening: Find validated binding data for fragments against a target

Does NOT Trigger

Scenario Use Instead
Curated bioactivity mining at scale, assay metadata, drug mechanisms alterlab-chembl
Compound identifiers/properties by name or CID, PubChem BioAssay alterlab-pubchem
Purchasable analogs or docking-ready 3D libraries alterlab-zinc-db
Docking a ligand into a receptor structure alterlab-diffdock

Core Capabilities

1. BindingDB REST API

Base URL: https://bindingdb.org/rest

import requests

BASE_URL = "https://bindingdb.org/rest"

def bindingdb_query(method, params):
    """Query the BindingDB REST API."""
    url = f"{BASE_URL}/{method}"
    response = requests.get(url, params=params, headers={"Accept": "application/json"})
    response.raise_for_status()
    return response.json()

2. Query by Target (UniProt ID)

The getLigandsByUniprot endpoint takes a single uniprot parameter formatted as <UniProt accession>;<cutoff in nM>, e.g. P00519;10000.

def get_ligands_for_target(uniprot_id, cutoff=10000):
    """
    Get all ligands with measured affinity for a UniProt target.

    Args:
        uniprot_id: UniProt accession (e.g., "P00519" for ABL1)
        cutoff: Maximum affinity value to return (in nM)
    """
    params = {
        "uniprot": f"{uniprot_id};{cutoff}",
        "response": "application/json",
    }
    return bindingdb_query("getLigandsByUniprot", params)

# Example: Get all compounds binding ABL1 (imatinib target) at <=100 nM
ligands = get_ligands_for_target("P00519", cutoff=100)

# Response shape (verified 2026-09): one top-level key, spelled
# "getLindsByUniprotResponse" (sic — getTargetByCompound uses the same key),
# holding "bdb.hit" (count, as a string) and "bdb.affinities": a list of
# {"bdb.monomerid", "bdb.smile", "bdb.affinity_type", "bdb.affinity"}.
# Affinities are strings, may carry leading spaces or ">"/"<" qualifiers.
resp = next(iter(ligands.values()))
rows = resp.get("bdb.affinities", [])

3. Query by SMILES (structural similarity)

def search_by_smiles(smiles, cutoff=0.85):
    """
    Search BindingDB by SMILES string (structural-similarity search).

    Args:
        smiles: SMILES string of the compound
        cutoff: Tanimoto similarity threshold (0.0-1.0; e.g. 0.85)
    """
    params = {
        "smiles": smiles,
        "cutoff": cutoff,
        "response": "application/json",
    }
    return bindingdb_query("getTargetByCompound", params)

# Example: structural-similarity search for imatinib's binding targets
result = search_by_smiles("Cc1ccc(NC(=O)c2ccc(CN3CCN(C)CC3)cc2)cc1Nc1nccc(-c2cccnc2)n1")

To query by a PubChem CID, first convert the CID to a SMILES string via PubChem PUG-REST, then pass it to search_by_smiles. There is no by-name or by-CID BindingDB REST endpoint.

4. Download-Based Analysis (Recommended for Large Queries)

For comprehensive analyses, download BindingDB data directly:

import pandas as pd

def load_bindingdb(filepath="BindingDB_All.tsv"):
    """
    Load BindingDB TSV file (the unzipped BindingDB_All_<YYYYMM>.tsv).
    Download the dated BindingDB_All_<YYYYMM>_tsv.zip from:
    https://www.bindingdb.org/rwd/bind/chemsearch/marvin/Download.jsp
    """
    # Key columns
    usecols = [
        "BindingDB Reactant_set_id",
        "Ligand SMILES",
        "Ligand InChI",
        "Ligand InChI Key",
        "BindingDB Target Chain  Sequence",
        "PDB ID(s) for Ligand-Target Complex",
        "UniProt (SwissProt) Entry Name of Target Chain",
        "UniProt (SwissProt) Primary ID of Target Chain",
        "UniProt (TrEMBL) Primary ID of Target Chain",
        "Ki (nM)",
        "IC50 (nM)",
        "Kd (nM)",
        "EC50 (nM)",
        "kon (M-1-s-1)",
        "koff (s-1)",
        "Target Name",
        "Target Source Organism According to Curator or DataSource",
        "Number of Protein Chains in Target (>1 implies a multichain complex)",
        "PubChem CID",
        "PubChem SID",
        "ChEMBL ID of Ligand",
        "DrugBank ID of Ligand",
    ]

    # Exact TSV headers drift between monthly releases (some contain double
    # spaces), so intersect with the actual header rather than hard-failing.
    header = pd.read_csv(filepath, sep="\t", nrows=0).columns
    keep = [c for c in usecols if c in header]
    df = pd.read_csv(filepath, sep="\t", usecols=keep,
                     low_memory=False, on_bad_lines='skip')

    # Convert affinity columns to numeric
    for col in ["Ki (nM)", "IC50 (nM)", "Kd (nM)", "EC50 (nM)"]:
        if col in df.columns:
            df[col] = pd.to_numeric(df[col], errors='coerce')

    return df

def query_target_affinity(df, uniprot_id, affinity_types=None, max_nm=10000):
    """Query loaded BindingDB for a specific target."""
    if affinity_types is None:
        affinity_types = ["Ki (nM)", "IC50 (nM)", "Kd (nM)"]

    # Filter by UniProt ID
    mask = df["UniProt (SwissProt) Primary ID of Target Chain"] == uniprot_id
    target_df = df[mask].copy()

    # Filter by affinity cutoff
    has_affinity = pd.Series(False, index=target_df.index)
    for col in affinity_types:
        if col in target_df.columns:
            has_affinity |= target_df[col] <= max_nm

    result = target_df[has_affinity][["Ligand SMILES"] + affinity_types +
                                      ["PubChem CID", "ChEMBL ID of Ligand"]].dropna(how='all')
    return result.sort_values(affinity_types[0])

5. SAR Analysis

import pandas as pd

def sar_analysis(df, target_uniprot, affinity_col="IC50 (nM)"):
    """
    Structure-activity relationship analysis for a target.
    Retrieves all compounds with affinity data and ranks by potency.
    """
    target_data = query_target_affinity(df, target_uniprot, [affinity_col])

    if target_data.empty:
        return target_data

    # Add pIC50 (negative log of IC50 in molar)
    if affinity_col in target_data.columns:
        target_data = target_data[target_data[affinity_col].notna()].copy()
        target_data["pAffinity"] = -((target_data[affinity_col] * 1e-9).apply(
            lambda x: __import__('math').log10(x)
        ))
        target_data = target_data.sort_values("pAffinity", ascending=False)

    return target_data

# Most potent compounds against EGFR (P00533)
# sar = sar_analysis(df, "P00533", "IC50 (nM)")
# print(sar.head(20))

6. Polypharmacology Profile

def polypharmacology_profile(df, ligand_smiles, affinity_cutoff_nM=1000):
    """
    Find all targets a compound binds to, by exact SMILES match.
    For tolerance to tautomers/salts/charge states, match on the InChIKey
    skeleton (first 14 chars of "Ligand InChI Key") instead of raw SMILES.
    """
    # Search by ligand SMILES (exact string match)
    mask = df["Ligand SMILES"] == ligand_smiles

    ligand_data = df[mask].copy()

    # Filter by affinity
    aff_cols = ["Ki (nM)", "IC50 (nM)", "Kd (nM)"]
    has_aff = pd.Series(False, index=ligand_data.index)
    for col in aff_cols:
        if col in ligand_data.columns:
            has_aff |= ligand_data[col] <= affinity_cutoff_nM

    result = ligand_data[has_aff][
        ["Target Name", "UniProt (SwissProt) Primary ID of Target Chain"] + aff_cols
    ].dropna(how='all')

    # Rank by the tightest measured constant per row (NaNs ignored)
    result = result.assign(best_nM=result[aff_cols].min(axis=1))
    return result.sort_values("best_nM")

Query Workflows

Workflow 1: Find Best Inhibitors for a Target

import pandas as pd

def find_best_inhibitors(uniprot_id, affinity_type="IC50 (nM)", top_n=20):
    """Find the most potent inhibitors for a target in BindingDB."""
    df = load_bindingdb("BindingDB_All.tsv")  # Load once and reuse
    result = query_target_affinity(df, uniprot_id, [affinity_type])

    if result.empty:
        print(f"No data found for {uniprot_id}")
        return result

    result = result.sort_values(affinity_type).head(top_n)
    print(f"Top {top_n} inhibitors for {uniprot_id} by {affinity_type}:")
    for _, row in result.iterrows():
        print(f"  {row['PubChem CID']}: {row[affinity_type]:.1f} nM | SMILES: {row['Ligand SMILES'][:40]}...")
    return result

Workflow 2: Selectivity Profiling

  1. Get all affinity data for your compound across all targets
  2. Compare affinity ratios between on-target and off-targets
  3. Identify selectivity cliffs (structural changes that improve selectivity)
  4. Cross-reference with ChEMBL for additional selectivity data

Workflow 3: Machine Learning Dataset Preparation

def prepare_ml_dataset(df, uniprot_ids, affinity_col="IC50 (nM)",
                        max_affinity_nM=100000, min_count=50):
    """Prepare BindingDB data for ML model training."""
    records = []
    for uid in uniprot_ids:
        target_df = query_target_affinity(df, uid, [affinity_col], max_affinity_nM)
        if len(target_df) >= min_count:
            target_df = target_df.copy()
            target_df["target"] = uid
            records.append(target_df)

    if not records:
        return pd.DataFrame()

    combined = pd.concat(records)
    # Add pAffinity (normalized)
    combined["pAffinity"] = -((combined[affinity_col] * 1e-9).apply(
        lambda x: __import__('math').log10(max(x, 1e-12))
    ))
    return combined[["Ligand SMILES", "target", "pAffinity", affinity_col]].dropna()

Key Data Fields

Field Description
Ligand SMILES 2D structure of the compound
Ligand InChI Key Unique chemical identifier
Ki (nM) Inhibition constant (equilibrium, functional)
Kd (nM) Dissociation constant (thermodynamic, binding)
IC50 (nM) Half-maximal inhibitory concentration
EC50 (nM) Half-maximal effective concentration
kon (M-1-s-1) Association rate constant
koff (s-1) Dissociation rate constant
UniProt (SwissProt) Primary ID Target UniProt accession
Target Name Protein name
PDB ID(s) for Ligand-Target Complex Crystal structures
PubChem CID PubChem compound ID
ChEMBL ID of Ligand ChEMBL compound ID

Affinity Interpretation

Affinity Classification Drug-likeness
< 1 nM Sub-nanomolar Very potent (picomolar range)
1–10 nM Nanomolar Potent, typical for approved drugs
10–100 nM Moderate Common lead compounds
100–1000 nM Weak Fragment/starting point
> 1000 nM Very weak Generally below drug-relevance threshold

Best Practices

  • Use Ki for direct binding: Ki reflects true binding affinity independent of enzymatic mechanism
  • IC50 context-dependency: IC50 values depend on substrate concentration (Cheng-Prusoff equation)
  • Watch for qualifier prefixes: TSV affinity cells often carry >, <, or >= prefixes (e.g. >10000) for censored measurements. pd.to_numeric(errors='coerce') silently turns these into NaN — strip the prefix first (e.g. df[col].astype(str).str.lstrip("<>= ")) and decide explicitly whether to keep, drop, or treat censored values as inequalities before modeling
  • Normalize units: BindingDB reports in nM; verify units when comparing across studies
  • Filter by target organism: Use Target Source Organism to ensure human protein data
  • Handle missing values: Not all compounds have all measurement types
  • Cross-reference with ChEMBL: ChEMBL has more curated activity data for medicinal chemistry

Additional Resources

Scripts

scripts/query_bindingdb.py — runnable helper for the BindingDB REST API (no key):

python scripts/query_bindingdb.py uniprot P00519 --cutoff 10000
python scripts/query_bindingdb.py pdb 1Q0L,3ANM --cutoff 100 --identity 92
python scripts/query_bindingdb.py compound "<SMILES>" --cutoff 0.85
Files (alterlab-academic-skills)
  • evals
    • evals.json 4.1 KB
      {
        "skill": "alterlab-bindingdb",
        "evals": [
          {
            "id": "ligands-by-target",
            "prompt": "I need every compound in BindingDB that binds ABL1 (UniProt P00519) with an affinity tighter than 100 nM, with their measured Ki/IC50 values. Can you pull that?",
            "expected_output": "Invokes alterlab-bindingdb. Uses the getLigandsByUniprot REST endpoint with the uniprot;cutoff parameter format (P00519;100), returning ligands and their measured binding constants (Ki, IC50, Kd) for the target. Explains the affinity cutoff is in nM.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "output_contains", "value": "P00519" }
            ]
          },
          {
            "id": "sar-potency-ranking",
            "prompt": "For an EGFR (P00533) inhibitor series I'm working on, retrieve all the experimental IC50 data from BindingDB and rank the analogs by potency so I can do an SAR analysis.",
            "expected_output": "Invokes alterlab-bindingdb for structure-activity relationship analysis. Loads BindingDB records for the target, filters by IC50, computes pIC50 / pAffinity, and ranks compounds from most to least potent. May note the difference between Ki and IC50 and Cheng-Prusoff context-dependency.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "behavior", "value": "Ranks compounds by measured potency and references SAR or pIC50/pAffinity." }
            ]
          },
          {
            "id": "polypharmacology-profile",
            "prompt": "Here's a kinase inhibitor SMILES. I want to know all the protein targets it has measured binding data against in BindingDB and at what affinities, to assess its polypharmacology.",
            "expected_output": "Invokes alterlab-bindingdb. Runs a structural-similarity search by SMILES (getTargetByCompound) or matches the ligand to find every target it binds, returning measured Ki/Kd/IC50 across targets to build a polypharmacology / selectivity profile.",
            "assertions": [
              { "type": "should_trigger", "value": true },
              { "type": "behavior", "value": "Returns multiple protein targets with measured affinities for the queried compound (polypharmacology)." }
            ]
          },
          {
            "id": "ml-dataset-prep",
            "prompt": "I want to train a binding-affinity prediction model. Can you assemble a curated set of protein-ligand IC50 measurements from BindingDB across several kinase targets, with SMILES and normalized pAffinity values?",
            "expected_output": "Invokes alterlab-bindingdb. Builds an ML-ready dataset of SMILES, target UniProt IDs, and normalized pAffinity values across the requested targets, filtering by affinity cutoff and minimum count per target as in the download-based BindingDB workflow.",
            "assertions": [
              { "type": "should_trigger", "value": true }
            ]
          },
          {
            "id": "near-miss-alterlab-chembl",
            "prompt": "I want the full curated medicinal-chemistry activity record for a compound series, including assay descriptions, ADMET annotations, and the ChEMBL activity comments and confidence scores.",
            "expected_output": "Does NOT invoke this skill; defers to alterlab-chembl. The user wants ChEMBL's curated medicinal-chemistry activity data with assay-level annotations and confidence scoring, not BindingDB's raw measured binding constants. BindingDB best-practice notes even recommend cross-referencing ChEMBL for curated medicinal-chemistry data.",
            "assertions": [
              { "type": "should_not_trigger", "value": true },
              { "type": "output_contains", "value": "alterlab-chembl" }
            ]
          },
          {
            "id": "near-miss-alterlab-pubchem",
            "prompt": "Convert this compound name to a CID and give me its molecular weight, XLogP, TPSA, and hydrogen-bond donor/acceptor counts.",
            "expected_output": "Does NOT invoke this skill; defers to alterlab-pubchem. The user wants physicochemical property lookup and name-to-CID conversion, which is PubChem's PUG-REST territory, not BindingDB's measured protein-ligand affinities.",
            "assertions": [
              { "type": "should_not_trigger", "value": true },
              { "type": "output_contains", "value": "alterlab-pubchem" }
            ]
          }
        ]
      }
      
  • references
    • affinity_queries.md 7.5 KB
      # BindingDB Affinity Query Reference
      
      ## Affinity Measurement Types
      
      ### Ki (Inhibition Constant)
      - **Definition**: Equilibrium constant for inhibitor-enzyme complex dissociation
      - **Equation**: Ki = [E][I]/[EI]
      - **Usage**: Enzyme inhibition; preferred for mechanistic studies
      - **Note**: Independent of substrate concentration (unlike IC50)
      
      ### Kd (Dissociation Constant)
      - **Definition**: Thermodynamic binding equilibrium constant
      - **Equation**: Kd = [A][B]/[AB]
      - **Usage**: Direct binding assays (SPR, ITC, fluorescence anisotropy)
      - **Note**: True measure of binding strength; lower = tighter binding
      
      ### IC50 (Half-Maximal Inhibitory Concentration)
      - **Definition**: Concentration of inhibitor that reduces target activity by 50%
      - **Usage**: Most common in drug discovery; assay-dependent
      - **Conversion to Ki**: Cheng-Prusoff equation: Ki = IC50 / (1 + [S]/Km)
      - **Note**: Depends on substrate concentration and assay conditions
      
      ### EC50 (Half-Maximal Effective Concentration)
      - **Definition**: Concentration that produces 50% of maximal effect
      - **Usage**: Cell-based assays, agonist studies
      
      ### Kinetics Parameters
      - **kon**: Association rate constant (M⁻¹s⁻¹); describes how fast complex forms
      - **koff**: Dissociation rate constant (s⁻¹); describes how fast complex dissociates
      - **Residence time**: τ = 1/koff; longer residence = more sustained effect
      - **Kd from kinetics**: Kd = koff/kon
      
      ## Common API Query Patterns
      
      ### By UniProt ID (REST API)
      
      ```python
      import requests
      
      def query_by_uniprot(uniprot_id, cutoff=10000):
          """
          REST API query for BindingDB affinities by UniProt target ID.
          The 'uniprot' param is formatted as '<accession>;<cutoff in nM>'.
          """
          url = "https://bindingdb.org/rest/getLigandsByUniprot"
          params = {
              "uniprot": f"{uniprot_id};{cutoff}",  # e.g. 'P00519;10000'
              "response": "application/json"
          }
          response = requests.get(url, params=params)
          return response.json()
      
      def query_by_uniprots(uniprot_ids, cutoff=10000):
          """
          Query multiple targets at once. 'uniprot' is a comma-separated list of
          accessions; 'cutoff' is the nM threshold passed separately.
          """
          url = "https://bindingdb.org/rest/getLigandsByUniprots"
          params = {
              "uniprot": ",".join(uniprot_ids),
              "cutoff": cutoff,
              "response": "application/json"
          }
          response = requests.get(url, params=params)
          return response.json()
      
      # Important targets
      COMMON_TARGETS = {
          "ABL1": "P00519",    # Imatinib, dasatinib target
          "EGFR": "P00533",    # Erlotinib, gefitinib target
          "BRAF": "P15056",    # Vemurafenib, dabrafenib target
          "CDK2": "P24941",    # Cell cycle kinase
          "HDAC1": "Q13547",   # Histone deacetylase
          "BRD4": "O60885",    # BET bromodomain reader
          "MDM2": "Q00987",    # p53 negative regulator
          "BCL2": "P10415",    # Antiapoptotic protein
          "PCSK9": "Q8NBP7",   # Cholesterol regulator
          "JAK2": "O60674",    # Cytokine signaling kinase
      }
      ```
      
      ### By Compound (REST API)
      
      BindingDB has no by-CID endpoint. To query by compound, use `getTargetByCompound`
      with a SMILES string (a structural-similarity search). If your input is a PubChem
      CID, first convert it to SMILES via PubChem PUG-REST.
      
      ```python
      def query_by_compound(smiles, cutoff=0.85):
          """Find targets for a compound by SMILES (structural-similarity search)."""
          url = "https://bindingdb.org/rest/getTargetByCompound"
          params = {
              "smiles": smiles,
              "cutoff": cutoff,        # Tanimoto similarity threshold (0.0-1.0)
              "response": "application/json"
          }
          response = requests.get(url, params=params)
          return response.json()
      
      def cid_to_smiles(cid):
          """Convert a PubChem CID to a canonical SMILES via PUG-REST."""
          url = (f"https://pubchem.ncbi.nlm.nih.gov/rest/pug/compound/cid/{cid}"
                 "/property/CanonicalSMILES/TXT")
          return requests.get(url).text.strip()
      
      # Example: Imatinib PubChem CID = 5291
      imatinib_data = query_by_compound(cid_to_smiles(5291))
      ```
      
      ### By PDB ID (REST API)
      
      There is no by-target-name endpoint. Query by UniProt ID (see above) or by PDB ID.
      
      ```python
      def query_by_pdb(pdb_ids, cutoff=100, identity=92):
          """Query BindingDB by PDB ID(s) (comma-separated)."""
          url = "https://bindingdb.org/rest/getLigandsByPDBs"
          params = {
              "pdb": ",".join(pdb_ids),   # e.g. '1Q0L,3ANM'
              "cutoff": cutoff,           # nM threshold
              "identity": identity,       # sequence-identity threshold (%)
              "response": "application/json"
          }
          response = requests.get(url, params=params)
          return response.json()
      ```
      
      ## Dataset Download Guide
      
      ### Available Files
      
      Files on the [downloads page](https://www.bindingdb.org/rwd/bind/chemsearch/marvin/Download.jsp)
      are dated (`<YYYYMM>`) and refreshed roughly monthly. Sizes below are for the
      2026-06 release; they grow slowly over time.
      
      | File | Size (zipped) | Contents |
      |------|------|---------|
      | `BindingDB_All_<YYYYMM>_tsv.zip` | ~560 MB | All data (~3.2M records), one TSV |
      | `BindingDB_All_2D_<YYYYMM>_sdf.zip` | ~1.5 GB | All data, 2D structures (SDF) |
      | `BindingDB_All_3D_<YYYYMM>_sdf.zip` | ~3 GB | All data, 3D structures (SDF) |
      | `BindingDB_Assays_<YYYYMM>_tsv.zip` | ~9 MB | Assay-level metadata |
      
      There is **no** split by affinity type. Source-specific subsets are offered
      instead (ChEMBL, Patents, PubChem, PDSP Ki, CSAR, ITC, ...), each as 2D/3D SDF
      and TSV. The full TSV contains all of Ki/IC50/Kd/EC50 in separate columns, so
      filter in pandas rather than looking for `BindingDB_Ki.tsv`-style files.
      For ML-ready, pre-split BindingDB subsets, see Therapeutics Data Commons (TDC),
      which repackages BindingDB separately (covered by the alterlab-pytdc skill).
      
      ### Efficient Loading
      
      ```python
      import pandas as pd
      
      # For large files, use chunking
      def load_bindingdb_chunked(filepath, uniprot_ids, affinity_col="Ki (nM)", chunk_size=100000):
          """Load BindingDB in chunks to filter for specific targets."""
          results = []
          for chunk in pd.read_csv(filepath, sep="\t", chunksize=chunk_size,
                                    low_memory=False, on_bad_lines='skip'):
              # Filter for target
              mask = chunk["UniProt (SwissProt) Primary ID of Target Chain"].isin(uniprot_ids)
              if mask.any():
                  results.append(chunk[mask])
      
          if results:
              return pd.concat(results)
          return pd.DataFrame()
      ```
      
      ## pKi / pIC50 Conversion
      
      Converting raw affinity to logarithmic scale (common in ML):
      
      ```python
      import numpy as np
      
      def to_log_affinity(affinity_nM):
          """Convert nM affinity to pAffinity (negative log molar)."""
          affinity_M = affinity_nM * 1e-9  # Convert nM to M
          return -np.log10(affinity_M)
      
      # Examples:
      # 1 nM   → pAffinity = 9.0
      # 10 nM  → pAffinity = 8.0
      # 100 nM → pAffinity = 7.0
      # 1 μM   → pAffinity = 6.0
      # 10 μM  → pAffinity = 5.0
      ```
      
      ## Quality Filters
      
      When using BindingDB data for ML or SAR:
      
      ```python
      def filter_quality(df):
          """Apply quality filters to BindingDB data."""
          # 1. Require valid SMILES
          df = df[df["Ligand SMILES"].notna() & (df["Ligand SMILES"] != "")]
      
          # 2. Require valid affinity
          df = df[df["Ki (nM)"].notna() | df["IC50 (nM)"].notna()]
      
          # 3. Filter extreme values (artifacts)
          for col in ["Ki (nM)", "IC50 (nM)", "Kd (nM)"]:
              if col in df.columns:
                  df = df[~(df[col] > 1e6)]  # Remove > 1 mM (non-specific)
      
          # 4. Use only human targets
          if "Target Source Organism According to Curator or DataSource" in df.columns:
              df = df[df["Target Source Organism According to Curator or DataSource"].str.contains(
                  "Homo sapiens", na=False
              )]
      
          return df
      ```
      
  • scripts
    • query_bindingdb.py 2.7 KB
      #!/usr/bin/env python3
      """Query the BindingDB public REST API (no API key required).
      
      REST base: https://bindingdb.org/rest
        - getLigandsByUniprot   uniprot='<acc>;<cutoff_nM>'
        - getLigandsByPDBs      pdb='<id1,id2>'  cutoff, identity
        - getTargetByCompound   smiles='<SMILES>'  cutoff (Tanimoto)
      
      Smoke test:
          uv run python query_bindingdb.py uniprot P00519 --cutoff 10000
          uv run python query_bindingdb.py pdb 1Q0L,3ANM --cutoff 100 --identity 92
      """
      import argparse
      import json
      
      import requests
      
      BASE = "https://bindingdb.org/rest"
      
      
      def by_uniprot(uniprot: str, cutoff: int = 10000) -> dict:
          """Ligands binding a target by UniProt accession (cutoff in nM)."""
          params = {"uniprot": f"{uniprot};{cutoff}", "response": "application/json"}
          r = requests.get(f"{BASE}/getLigandsByUniprot", params=params, timeout=60)
          r.raise_for_status()
          return r.json()
      
      
      def by_pdb(pdb_ids: str, cutoff: int = 100, identity: int = 92) -> dict:
          """Ligands by one or more comma-separated PDB IDs."""
          params = {"pdb": pdb_ids, "cutoff": cutoff, "identity": identity,
                    "response": "application/json"}
          r = requests.get(f"{BASE}/getLigandsByPDBs", params=params, timeout=60)
          r.raise_for_status()
          return r.json()
      
      
      def by_compound(smiles: str, cutoff: float = 0.85) -> dict:
          """Targets for a compound via SMILES structural-similarity search."""
          params = {"smiles": smiles, "cutoff": cutoff, "response": "application/json"}
          r = requests.get(f"{BASE}/getTargetByCompound", params=params, timeout=60)
          r.raise_for_status()
          return r.json()
      
      
      def main() -> None:
          p = argparse.ArgumentParser(description="Query BindingDB REST (no key required).")
          sub = p.add_subparsers(dest="cmd", required=True)
      
          pu = sub.add_parser("uniprot", help="Query ligands by UniProt accession")
          pu.add_argument("accession")
          pu.add_argument("--cutoff", type=int, default=10000, help="affinity cutoff (nM)")
      
          pp = sub.add_parser("pdb", help="Query ligands by PDB ID(s)")
          pp.add_argument("pdb_ids", help="comma-separated PDB IDs, e.g. 1Q0L,3ANM")
          pp.add_argument("--cutoff", type=int, default=100)
          pp.add_argument("--identity", type=int, default=92)
      
          pc = sub.add_parser("compound", help="Query targets by SMILES")
          pc.add_argument("smiles")
          pc.add_argument("--cutoff", type=float, default=0.85, help="Tanimoto threshold")
      
          args = p.parse_args()
          if args.cmd == "uniprot":
              out = by_uniprot(args.accession, args.cutoff)
          elif args.cmd == "pdb":
              out = by_pdb(args.pdb_ids, args.cutoff, args.identity)
          else:
              out = by_compound(args.smiles, args.cutoff)
          print(json.dumps(out, indent=2))
      
      
      if __name__ == "__main__":
          main()
      
  • SKILL.md 14.6 KB
    ---
    name: alterlab-bindingdb
    description: Query BindingDB for measured protein-ligand binding affinities (Ki, Kd, IC50, EC50) via its keyless REST API or the full TSV download, searching by target (UniProt ID), compound (SMILES), or pathogen. Use when looking up experimental binding constants, profiling inhibitors of a protein target, doing lead optimization, polypharmacology analysis, or structure-activity relationship (SAR) studies; for curated bioactivity mining or drug-like compound library screening at scale prefer alterlab-chembl instead. Part of the AlterLab Academic Skills suite.
    license: CC-BY-3.0
    allowed-tools: Read WebFetch Bash(curl:*) Bash(python:*)
    compatibility: Keyless public BindingDB web services (no authentication required)
    metadata:
        skill-author: AlterLab
        version: "1.0.1"
        last_updated: "2026-09-23"
    ---
    
    # BindingDB Database
    
    ## Overview
    
    BindingDB (https://www.bindingdb.org/) is the primary public database of measured drug-protein binding affinities. It contains roughly 3.2 million binding data records for ~1.4 million compounds tested against ~11,500 protein targets (homepage figures, 2026-09), curated from scientific literature and patent literature. BindingDB stores quantitative binding measurements (Ki, Kd, IC50, EC50) essential for drug discovery, pharmacology, and computational chemistry research.
    
    **Key resources:**
    - BindingDB website: https://www.bindingdb.org/
    - REST API base: https://bindingdb.org/rest/ (no key; default response is XML, append `response=application/json`)
    - Downloads page: https://www.bindingdb.org/rwd/bind/chemsearch/marvin/Download.jsp (the full TSV is the dated `BindingDB_All_<YYYYMM>_tsv.zip`, ~560 MB zipped, refreshed monthly)
    
    ## When to Use This Skill
    
    Use BindingDB when:
    
    - **Target-based drug discovery**: What known compounds bind to a target protein? What are their affinities?
    - **SAR analysis**: How do structural modifications affect binding affinity for a series of analogs?
    - **Lead compound profiling**: What targets does a compound bind (selectivity/polypharmacology)?
    - **Benchmark datasets**: Obtain curated protein-ligand affinity data for ML model training
    - **Repurposing analysis**: Does an approved drug bind to an unintended target?
    - **Competitive analysis**: What is the best reported affinity for a target class?
    - **Fragment screening**: Find validated binding data for fragments against a target
    
    ### Does NOT Trigger
    
    | Scenario | Use Instead |
    |----------|-------------|
    | Curated bioactivity mining at scale, assay metadata, drug mechanisms | `alterlab-chembl` |
    | Compound identifiers/properties by name or CID, PubChem BioAssay | `alterlab-pubchem` |
    | Purchasable analogs or docking-ready 3D libraries | `alterlab-zinc-db` |
    | Docking a ligand into a receptor structure | `alterlab-diffdock` |
    
    ## Core Capabilities
    
    ### 1. BindingDB REST API
    
    Base URL: `https://bindingdb.org/rest`
    
    ```python
    import requests
    
    BASE_URL = "https://bindingdb.org/rest"
    
    def bindingdb_query(method, params):
        """Query the BindingDB REST API."""
        url = f"{BASE_URL}/{method}"
        response = requests.get(url, params=params, headers={"Accept": "application/json"})
        response.raise_for_status()
        return response.json()
    ```
    
    ### 2. Query by Target (UniProt ID)
    
    The `getLigandsByUniprot` endpoint takes a single `uniprot` parameter formatted as
    `<UniProt accession>;<cutoff in nM>`, e.g. `P00519;10000`.
    
    ```python
    def get_ligands_for_target(uniprot_id, cutoff=10000):
        """
        Get all ligands with measured affinity for a UniProt target.
    
        Args:
            uniprot_id: UniProt accession (e.g., "P00519" for ABL1)
            cutoff: Maximum affinity value to return (in nM)
        """
        params = {
            "uniprot": f"{uniprot_id};{cutoff}",
            "response": "application/json",
        }
        return bindingdb_query("getLigandsByUniprot", params)
    
    # Example: Get all compounds binding ABL1 (imatinib target) at <=100 nM
    ligands = get_ligands_for_target("P00519", cutoff=100)
    
    # Response shape (verified 2026-09): one top-level key, spelled
    # "getLindsByUniprotResponse" (sic — getTargetByCompound uses the same key),
    # holding "bdb.hit" (count, as a string) and "bdb.affinities": a list of
    # {"bdb.monomerid", "bdb.smile", "bdb.affinity_type", "bdb.affinity"}.
    # Affinities are strings, may carry leading spaces or ">"/"<" qualifiers.
    resp = next(iter(ligands.values()))
    rows = resp.get("bdb.affinities", [])
    ```
    
    ### 3. Query by SMILES (structural similarity)
    
    ```python
    def search_by_smiles(smiles, cutoff=0.85):
        """
        Search BindingDB by SMILES string (structural-similarity search).
    
        Args:
            smiles: SMILES string of the compound
            cutoff: Tanimoto similarity threshold (0.0-1.0; e.g. 0.85)
        """
        params = {
            "smiles": smiles,
            "cutoff": cutoff,
            "response": "application/json",
        }
        return bindingdb_query("getTargetByCompound", params)
    
    # Example: structural-similarity search for imatinib's binding targets
    result = search_by_smiles("Cc1ccc(NC(=O)c2ccc(CN3CCN(C)CC3)cc2)cc1Nc1nccc(-c2cccnc2)n1")
    ```
    
    To query by a PubChem CID, first convert the CID to a SMILES string via PubChem
    PUG-REST, then pass it to `search_by_smiles`. There is no by-name or by-CID
    BindingDB REST endpoint.
    
    ### 4. Download-Based Analysis (Recommended for Large Queries)
    
    For comprehensive analyses, download BindingDB data directly:
    
    ```python
    import pandas as pd
    
    def load_bindingdb(filepath="BindingDB_All.tsv"):
        """
        Load BindingDB TSV file (the unzipped BindingDB_All_<YYYYMM>.tsv).
        Download the dated BindingDB_All_<YYYYMM>_tsv.zip from:
        https://www.bindingdb.org/rwd/bind/chemsearch/marvin/Download.jsp
        """
        # Key columns
        usecols = [
            "BindingDB Reactant_set_id",
            "Ligand SMILES",
            "Ligand InChI",
            "Ligand InChI Key",
            "BindingDB Target Chain  Sequence",
            "PDB ID(s) for Ligand-Target Complex",
            "UniProt (SwissProt) Entry Name of Target Chain",
            "UniProt (SwissProt) Primary ID of Target Chain",
            "UniProt (TrEMBL) Primary ID of Target Chain",
            "Ki (nM)",
            "IC50 (nM)",
            "Kd (nM)",
            "EC50 (nM)",
            "kon (M-1-s-1)",
            "koff (s-1)",
            "Target Name",
            "Target Source Organism According to Curator or DataSource",
            "Number of Protein Chains in Target (>1 implies a multichain complex)",
            "PubChem CID",
            "PubChem SID",
            "ChEMBL ID of Ligand",
            "DrugBank ID of Ligand",
        ]
    
        # Exact TSV headers drift between monthly releases (some contain double
        # spaces), so intersect with the actual header rather than hard-failing.
        header = pd.read_csv(filepath, sep="\t", nrows=0).columns
        keep = [c for c in usecols if c in header]
        df = pd.read_csv(filepath, sep="\t", usecols=keep,
                         low_memory=False, on_bad_lines='skip')
    
        # Convert affinity columns to numeric
        for col in ["Ki (nM)", "IC50 (nM)", "Kd (nM)", "EC50 (nM)"]:
            if col in df.columns:
                df[col] = pd.to_numeric(df[col], errors='coerce')
    
        return df
    
    def query_target_affinity(df, uniprot_id, affinity_types=None, max_nm=10000):
        """Query loaded BindingDB for a specific target."""
        if affinity_types is None:
            affinity_types = ["Ki (nM)", "IC50 (nM)", "Kd (nM)"]
    
        # Filter by UniProt ID
        mask = df["UniProt (SwissProt) Primary ID of Target Chain"] == uniprot_id
        target_df = df[mask].copy()
    
        # Filter by affinity cutoff
        has_affinity = pd.Series(False, index=target_df.index)
        for col in affinity_types:
            if col in target_df.columns:
                has_affinity |= target_df[col] <= max_nm
    
        result = target_df[has_affinity][["Ligand SMILES"] + affinity_types +
                                          ["PubChem CID", "ChEMBL ID of Ligand"]].dropna(how='all')
        return result.sort_values(affinity_types[0])
    ```
    
    ### 5. SAR Analysis
    
    ```python
    import pandas as pd
    
    def sar_analysis(df, target_uniprot, affinity_col="IC50 (nM)"):
        """
        Structure-activity relationship analysis for a target.
        Retrieves all compounds with affinity data and ranks by potency.
        """
        target_data = query_target_affinity(df, target_uniprot, [affinity_col])
    
        if target_data.empty:
            return target_data
    
        # Add pIC50 (negative log of IC50 in molar)
        if affinity_col in target_data.columns:
            target_data = target_data[target_data[affinity_col].notna()].copy()
            target_data["pAffinity"] = -((target_data[affinity_col] * 1e-9).apply(
                lambda x: __import__('math').log10(x)
            ))
            target_data = target_data.sort_values("pAffinity", ascending=False)
    
        return target_data
    
    # Most potent compounds against EGFR (P00533)
    # sar = sar_analysis(df, "P00533", "IC50 (nM)")
    # print(sar.head(20))
    ```
    
    ### 6. Polypharmacology Profile
    
    ```python
    def polypharmacology_profile(df, ligand_smiles, affinity_cutoff_nM=1000):
        """
        Find all targets a compound binds to, by exact SMILES match.
        For tolerance to tautomers/salts/charge states, match on the InChIKey
        skeleton (first 14 chars of "Ligand InChI Key") instead of raw SMILES.
        """
        # Search by ligand SMILES (exact string match)
        mask = df["Ligand SMILES"] == ligand_smiles
    
        ligand_data = df[mask].copy()
    
        # Filter by affinity
        aff_cols = ["Ki (nM)", "IC50 (nM)", "Kd (nM)"]
        has_aff = pd.Series(False, index=ligand_data.index)
        for col in aff_cols:
            if col in ligand_data.columns:
                has_aff |= ligand_data[col] <= affinity_cutoff_nM
    
        result = ligand_data[has_aff][
            ["Target Name", "UniProt (SwissProt) Primary ID of Target Chain"] + aff_cols
        ].dropna(how='all')
    
        # Rank by the tightest measured constant per row (NaNs ignored)
        result = result.assign(best_nM=result[aff_cols].min(axis=1))
        return result.sort_values("best_nM")
    ```
    
    ## Query Workflows
    
    ### Workflow 1: Find Best Inhibitors for a Target
    
    ```python
    import pandas as pd
    
    def find_best_inhibitors(uniprot_id, affinity_type="IC50 (nM)", top_n=20):
        """Find the most potent inhibitors for a target in BindingDB."""
        df = load_bindingdb("BindingDB_All.tsv")  # Load once and reuse
        result = query_target_affinity(df, uniprot_id, [affinity_type])
    
        if result.empty:
            print(f"No data found for {uniprot_id}")
            return result
    
        result = result.sort_values(affinity_type).head(top_n)
        print(f"Top {top_n} inhibitors for {uniprot_id} by {affinity_type}:")
        for _, row in result.iterrows():
            print(f"  {row['PubChem CID']}: {row[affinity_type]:.1f} nM | SMILES: {row['Ligand SMILES'][:40]}...")
        return result
    ```
    
    ### Workflow 2: Selectivity Profiling
    
    1. Get all affinity data for your compound across all targets
    2. Compare affinity ratios between on-target and off-targets
    3. Identify selectivity cliffs (structural changes that improve selectivity)
    4. Cross-reference with ChEMBL for additional selectivity data
    
    ### Workflow 3: Machine Learning Dataset Preparation
    
    ```python
    def prepare_ml_dataset(df, uniprot_ids, affinity_col="IC50 (nM)",
                            max_affinity_nM=100000, min_count=50):
        """Prepare BindingDB data for ML model training."""
        records = []
        for uid in uniprot_ids:
            target_df = query_target_affinity(df, uid, [affinity_col], max_affinity_nM)
            if len(target_df) >= min_count:
                target_df = target_df.copy()
                target_df["target"] = uid
                records.append(target_df)
    
        if not records:
            return pd.DataFrame()
    
        combined = pd.concat(records)
        # Add pAffinity (normalized)
        combined["pAffinity"] = -((combined[affinity_col] * 1e-9).apply(
            lambda x: __import__('math').log10(max(x, 1e-12))
        ))
        return combined[["Ligand SMILES", "target", "pAffinity", affinity_col]].dropna()
    ```
    
    ## Key Data Fields
    
    | Field | Description |
    |-------|-------------|
    | `Ligand SMILES` | 2D structure of the compound |
    | `Ligand InChI Key` | Unique chemical identifier |
    | `Ki (nM)` | Inhibition constant (equilibrium, functional) |
    | `Kd (nM)` | Dissociation constant (thermodynamic, binding) |
    | `IC50 (nM)` | Half-maximal inhibitory concentration |
    | `EC50 (nM)` | Half-maximal effective concentration |
    | `kon (M-1-s-1)` | Association rate constant |
    | `koff (s-1)` | Dissociation rate constant |
    | `UniProt (SwissProt) Primary ID` | Target UniProt accession |
    | `Target Name` | Protein name |
    | `PDB ID(s) for Ligand-Target Complex` | Crystal structures |
    | `PubChem CID` | PubChem compound ID |
    | `ChEMBL ID of Ligand` | ChEMBL compound ID |
    
    ## Affinity Interpretation
    
    | Affinity | Classification | Drug-likeness |
    |----------|---------------|---------------|
    | < 1 nM | Sub-nanomolar | Very potent (picomolar range) |
    | 1–10 nM | Nanomolar | Potent, typical for approved drugs |
    | 10–100 nM | Moderate | Common lead compounds |
    | 100–1000 nM | Weak | Fragment/starting point |
    | > 1000 nM | Very weak | Generally below drug-relevance threshold |
    
    ## Best Practices
    
    - **Use Ki for direct binding**: Ki reflects true binding affinity independent of enzymatic mechanism
    - **IC50 context-dependency**: IC50 values depend on substrate concentration (Cheng-Prusoff equation)
    - **Watch for qualifier prefixes**: TSV affinity cells often carry `>`, `<`, or `>=` prefixes (e.g. `>10000`) for censored measurements. `pd.to_numeric(errors='coerce')` silently turns these into NaN — strip the prefix first (e.g. `df[col].astype(str).str.lstrip("<>= ")`) and decide explicitly whether to keep, drop, or treat censored values as inequalities before modeling
    - **Normalize units**: BindingDB reports in nM; verify units when comparing across studies
    - **Filter by target organism**: Use `Target Source Organism` to ensure human protein data
    - **Handle missing values**: Not all compounds have all measurement types
    - **Cross-reference with ChEMBL**: ChEMBL has more curated activity data for medicinal chemistry
    
    ## Additional Resources
    
    - **BindingDB website**: https://www.bindingdb.org/
    - **Data downloads**: https://www.bindingdb.org/rwd/bind/chemsearch/marvin/Download.jsp
    - **REST API documentation**: https://www.bindingdb.org/rwd/bind/BindingDBRESTfulAPI.jsp (REST base: https://bindingdb.org/rest)
    - **Citation**: Liu T et al. "BindingDB in 2024: a FAIR knowledgebase of protein-small molecule binding data." Nucleic Acids Research 2025;53(D1):D1633-D1644. doi:10.1093/nar/gkae1075 (earlier: Gilson MK et al., NAR 2016;44(D1):D1045-53, doi:10.1093/nar/gkv1072)
    - **Related resources**: ChEMBL (https://www.ebi.ac.uk/chembl/), PubChem BioAssay
    
    ## Scripts
    
    `scripts/query_bindingdb.py` — runnable helper for the BindingDB REST API (no key):
    
    ```bash
    python scripts/query_bindingdb.py uniprot P00519 --cutoff 10000
    python scripts/query_bindingdb.py pdb 1Q0L,3ANM --cutoff 100 --identity 92
    python scripts/query_bindingdb.py compound "<SMILES>" --cutoff 0.85
    ```
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related