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
Install
npx skills add https://github.com/AlterLab-IEU/AlterLab-Academic-Skills/tree/main/skills/databases/alterlab-bindingdb
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install alterlab-ieu-alterlab-academic-skills@llmmart
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:
- 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
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
- Get all affinity data for your compound across all targets
- Compare affinity ratios between on-target and off-targets
- Identify selectivity cliffs (structural changes that improve selectivity)
- 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 Organismto 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):
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.
Reviews (0)
No reviews yet.
No comments yet.