Skip to main content
Home
AboutUser Guide
All tutorials
TutorialadvancedChEMBLBioactivityMachine Learning

Listing ID: TU-VC2TQ

Written and reviewed by the Allbio.AI team. Educational content only — it is general guidance, not professional, clinical, or regulatory advice. AI outputs can be wrong — always validate results before using them in regulated, clinical, or production workflows.

Modelling EGFR bioactivity with ChEMBL and simple ML

By Allbio.AI Team· Posted 22 Jul 2026· Updated on 23 Sep 2026

Build a lightweight bioactivity classifier from public ChEMBL data and interpret the results — an educational example, not a drug-likeness or ADMET prediction.

Before you paste anything into a hosted AI tool: remove patient data and PHI, unpublished or licensed material, controlled-access datasets, and anything under an NDA. Vendor data-handling and retention terms change — check the current terms against your own organization's policy rather than relying on what a tutorial says.


TITLE: Predicting target bioactivity with ChEMBL and simple ML

A quick primer before the walkthrough:

In plain English: Build a lightweight target bioactivity classifier (using EGFR IC50 data as an educational example) from public ChEMBL data and interpret the results — an entry-level end-to-end ML workflow for computational biology and chemistry teams.

What the numbers mean: IC50 is the concentration of a compound needed to inhibit a target by half; a smaller IC50 means a more potent compound. nM stands for nanomolar — a concentration unit, one-billionth of a mole per liter — and is commonly used to report assay potency.

Note: This model predicts target-specific bioactivity as an illustrative demonstration, not ADMET or drug-likeness profiles, and is not validated for clinical, diagnostic, or drug-development decisions.

Key terms:

  • Bioactivity Prediction — Using machine learning to evaluate whether a compound meets an in vitro activity threshold (e.g., target inhibition) in an illustrative computational model.
  • ChEMBL — A public database of bioactive molecules and their measured assay properties.
  • Classifier — An ML model that assigns a class label (e.g., "active" vs "inactive").

What you'll need: Python, RDKit, scikit-learn, and a Colab or local Jupyter setup.

Comfort level: Comfortable with Python and basic ML terms. Advanced — if you're new to AI, read the primers first.


New to this? In plain terms, you download measurements of how strongly known compounds hit a target, turn each molecule into a row of numbers, and train a simple model to guess "active" or "not active" for new molecules. Every step below is a normal Python script — if you have never trained a model, run it once exactly as written before changing anything.

What you'll build

A binary bioactivity classifier trained on ChEMBL EGFR assay data using RDKit descriptors and scikit-learn — an educational demonstration of cheminformatics ML workflows done with illustrative validation practices.

Disclaimer: This tutorial and code are for research and educational purposes only. They do not constitute medical, clinical, diagnostic, legal, or therapeutic advice, and are not intended, certified, or validated for clinical, diagnostic, safety, or regulatory drug-development decisions. All assay parameters, activity thresholds (such as the illustrative IC50 cutoff below), and quality filters are presented as non-prescriptive examples and must be adapted to your organization's specific SOPs, experimental protocols, and expert domain judgment.

1. Install

pip install rdkit chembl_webresource_client scikit-learn pandas

2. Pull a bioactivity dataset

Target: EGFR (CHEMBL203). As an illustrative example, we will classify compounds as active (IC50 of 1000 nM or less, an arbitrary teaching threshold) versus inactive. To support data consistency, retrieve metadata like assay_type and confidence_score, and aggregate duplicate assays to median values per compound according to your team's protocol requirements.

import pandas as pd
from chembl_webresource_client.new_client import new_client

# Retrieve IC50 records with assay metadata (illustrative query)
act = new_client.activity.filter(
    target_chembl_id="CHEMBL203",
    standard_type="IC50",
    standard_units="nM",
).only(["canonical_smiles", "standard_value", "assay_type", "confidence_score"])

df = pd.DataFrame(list(act)).dropna(subset=["canonical_smiles", "standard_value"])

# Filter for confidence and valid numeric values (illustrative parameter settings)
df["nM"] = pd.to_numeric(df["standard_value"], errors="coerce")
df["confidence_score"] = pd.to_numeric(df["confidence_score"], errors="coerce")
df = df.query("nM > 0 and nM < 1e7 and confidence_score >= 8")

# Aggregate duplicate compound measurements to median IC50 before binarizing
df = df.groupby("canonical_smiles", as_index=False)["nM"].median()
# Illustrative classification threshold (1000 nM); adjust according to internal SOPs
df["active"] = (df["nM"] <= 1000).astype(int)

3. Compute descriptors

Extract features while maintaining alignment between molecular features and target labels:

from rdkit import Chem
from rdkit.Chem import Descriptors, AllChem
import numpy as np

def featurize(smi):
    m = Chem.MolFromSmiles(smi)
    if m is None:
        return None
    fp = AllChem.GetMorganFingerprintAsBitVect(m, 2, nBits=1024)
    arr = np.zeros(1024, dtype=int)
    AllChem.DataStructs.ConvertToNumpyArray(fp, arr)
    props = [
        Descriptors.MolWt(m),
        Descriptors.MolLogP(m),
        Descriptors.NumHDonors(m),
        Descriptors.NumHAcceptors(m),
        Descriptors.TPSA(m)
    ]
    return np.concatenate([arr, props])

# Filter and align valid molecules and features cleanly
valid_features = []
valid_indices = []

for idx, smi in enumerate(df["canonical_smiles"]):
    feat = featurize(smi)
    if feat is not None:
        valid_features.append(feat)
        valid_indices.append(idx)

X = np.vstack(valid_features)
df_valid = df.iloc[valid_indices].reset_index(drop=True)
y = df_valid["active"].values

4. Train + evaluate — scaffold split, not random

Random splits can leak structural analogs across train and test sets, inflating performance estimates. Use scaffold splitting to group molecules by Bemis-Murcko scaffolds as an illustrative validation approach:

from rdkit.Chem.Scaffolds.MurckoScaffold import MurckoScaffoldSmiles
from collections import defaultdict

# Group indices by core scaffold
scaffolds = defaultdict(list)
for i, smi in enumerate(df_valid["canonical_smiles"]):
    s = MurckoScaffoldSmiles(smi, includeChirality=False)
    scaffolds[s].append(i)

# Allocate scaffold clusters to train/test without heavy target overshoot
groups = list(scaffolds.values())
groups.sort(key=len, reverse=True)

train_idx, test_idx = [], []
target_test_count = 0.2 * len(df_valid)

for g in groups:
    if len(test_idx) + len(g) <= target_test_count or len(test_idx) == 0:
        test_idx.extend(g)
    else:
        train_idx.extend(g)

Then train and evaluate the model:

from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score, classification_report

clf = RandomForestClassifier(n_estimators=500, n_jobs=-1, class_weight="balanced", random_state=42)
clf.fit(X[train_idx], y[train_idx])
pred = clf.predict_proba(X[test_idx])[:, 1]

print("AUC:", roc_auc_score(y[test_idx], pred))
print(classification_report(y[test_idx], pred > 0.5))

Note: In single-target bioactivity benchmarks (such as the MoleculeNet benchmark suite published by Wu et al. in 2018), scaffold splits reported ROC-AUC values in the range of 0.70–0.85 as of 2018 depending on filtering criteria, target difficulty, and model parameters. High scores on random splits frequently reflect structural analog leakage rather than generalizable predictive performance.

5. Where an LLM fits

You can use an LLM as an educational aid to summarize feature importances after mapping top Morgan bits back to molecular substructures (Chem.Draw.DrawMorganBit). For example:

"Given these SMARTS patterns for the top 10 predictive bits and their feature importance rankings, provide an educational summary of the common chemical functional groups they represent for literature contextualization."

Caution: LLM output is strictly for illustrative feature annotation and literature exploration. It does not constitute medicinal chemistry advice, therapeutic guidance, or experimental direction. All chemical interpretations, hypotheses, and assay designs must be evaluated by qualified subject matter experts in accordance with your organization's internal SOPs and regulatory framework.

Pitfalls

  • Avoid random splits for analogs: Standard random splits often leak scaffold variants; scaffold or temporal splits can be used for more realistic evaluation based on your team's validation protocol.
  • Aggregate assay duplicates: ChEMBL contains repeated assays for identical compounds; aggregate to median activity values (e.g., median IC50) or apply internal data-cleaning rules rather than dropping duplicates arbitrarily.
  • Check assay metadata: Filter or report assay_type and confidence_score — mixing binding and functional assays or low-confidence entries can degrade model reliability.
  • Context-specific thresholds: Activity cutoffs (such as the illustrative 1000 nM value used above) are non-prescriptive parameters and should be adjusted to align with your project's specific assay conditions, target biology, and internal SOPs.

If something goes wrong

  • The ChEMBL query returns nothing or times out. Check the target ID and re-run; public APIs throttle. Print the number of rows after every filter so you can see where the data disappeared.
  • RDKit cannot parse some SMILES. That is normal in public data. Drop the failures and log how many you dropped — never silently.
  • Accuracy looks suspiciously high. Almost always leakage: near-identical molecules in both train and test. That is exactly why the scaffold split above exists; a random split will flatter you.
  • The classes are very unbalanced. If 90% of compounds are inactive, a model that always says "inactive" scores 90%. Report balanced accuracy, ROC-AUC, or precision/recall instead.
  • The model disagrees with a chemist. Believe the chemist first. This is a triage aid for ranking, not a decision tool.

Plain-English glossary

  • IC50 — the concentration of a compound needed to inhibit a target by half; a smaller value means a more potent compound.
  • Descriptor / fingerprint — the row of numbers describing a molecule so software can compare structures.
  • Scaffold — the shared core skeleton of a family of related molecules.
  • Train/test split — holding data back so you can check the model on molecules it never saw.
  • Leakage — accidentally letting test information into training, which inflates scores.
  • ROC-AUC — a 0.5–1.0 score for how well the model ranks actives above inactives.

Found this helpful? Browse more tutorials.

Discussion

Screened for our content standards

Questions or experience with this tutorial? Share what worked, what didn't, or add context. Keep it related to AI in the life sciences. Comments are not medical, clinical, legal, or investment advice. No ads or sales pitches.

Discussion is open to Allbio.AI members

Sign in to read comments and add your own. Not a member yet? Join the community.