Which molecules stop the SARS-CoV-2 protease?

Machine-learning classifiers that separate active from inactive 3C-like proteinase inhibitors, trained on experimental ChEMBL data.

Where do you draw the line between active and inactive?

Every dot is a molecule placed by its measured IC50. Lower means it blocks the enzyme at a smaller dose. Drag the two lines, or use the sliders, and watch the labels, and the class balance, change. This is the labelling step of the project.

  1. ChEMBLMeasured IC50 values
  2. Clean and labelActive or inactive
  3. Morgan fingerprintsRDKit features
  4. Three classifiersRF, XGBoost, LightGBM
  5. Streamlit appExplore and predict

The problem

The 3C-like protease (3CLpro) is the enzyme SARS-CoV-2 uses to cut its long protein chain into working parts. Block it and the virus cannot replicate, which makes it a major drug target.

Screening every candidate in the lab is slow and costly. A model that scores molecules as likely active or inactive helps choose which ones deserve bench time.

What I built

An end-to-end cheminformatics workflow: it pulls measured IC50 values from ChEMBL, turns each molecule into a fingerprint, trains three classifiers and compares them side by side.

The results are explorable in a Streamlit app, and the whole analysis is in a notebook you can run.

Where an IC50 comes from

Each molecule is tested at a range of concentrations and the enzyme's remaining activity is measured. Fitting a dose-response curve gives the concentration that halves the activity, the IC50. Change the potency and the steepness of the curve, and the label it earns follows the cutoffs you set in the first plot.

Inhibition at fixed concentrations
Reading the curve

Hill equation
inhibition % = 100 / (1 + 10^((logIC50 − logC) × slope))

How the pipeline works

  1. Retrieve

    Query ChEMBL Web Services for 3CLpro bioactivity records and extract measured IC50 values.

    See the code
    records = new_client.activity.filter(
        target_chembl_id=TARGET_ID, standard_type="IC50")
    raw = pd.DataFrame(list(records))
  2. Clean and label

    Keep exact nM measurements, average duplicates, then label by IC50 cutoff, as in the interactive plot above.

    See the code
    df = df[(df.ic50_nM <= ACTIVE_NM) | (df.ic50_nM >= INACTIVE_NM)]
    df["active"] = (df.ic50_nM <= ACTIVE_NM).astype(int)
  3. Featurize

    Turn every molecule into a fixed-length Morgan fingerprint with RDKit.

    See the code
    gen = rdFingerprintGenerator.GetMorganGenerator(radius=2, fpSize=2048)
    X = gen.GetFingerprintAsNumPy(Chem.MolFromSmiles(smiles))
  4. Train

    Fit Random Forest, XGBoost and LightGBM on a stratified train and test split.

    See the code
    Xtr, Xte, ytr, yte = train_test_split(X, y, test_size=0.2, stratify=y)
    for name, model in models.items():
        model.fit(Xtr, ytr)
  5. Evaluate

    Compare AUC-ROC, F1-score and complementary metrics to check discrimination across both classes.

    See the code
    p = model.predict_proba(Xte)[:, 1]
    roc_auc_score(yte, p), f1_score(yte, p >= 0.5)
  6. Explore

    Use Seaborn plots and a Streamlit app to inspect molecular features, predictions and classifier performance.

Properties of active and inactive molecules

Do potent molecules look different from weak ones? These are standard drug-likeness descriptors computed with RDKit for the curated molecules, split by the cutoffs you set at the top of the page. Move those cutoffs and every panel here updates.

activeinactive
Molecular weight against LogP, Lipinski limits shaded
All properties at the current cutoffs

How a molecule becomes numbers

Morgan fingerprints describe each atom by the neighbourhood around it. Radius 0 is the atom alone, and each step outward adds the atoms one bond further away. Every neighbourhood is hashed into a bit position, so a whole molecule becomes a fixed-length row of 0s and 1s. These are real compounds from the dataset, fingerprinted with RDKit.

Environments around the selected atom
Share of set bits first created at each radius
radius 0radius 1radius 2radius 3

Resulting fingerprint for the whole molecule

Similar molecules share bits

The Tanimoto score is the share of set bits two molecules have in common, and it is one way to ask how alike two compounds are. The table lists real compounds from the dataset, picked to be structurally diverse. Select a cell in the heat map to compare a pair.

Highest and lowest values are off-diagonal pairs — the diagonal is each molecule against itself (1.00). Select any map cell or any row of the pair table to compare that pair below.

Spread of the pair values
Tanimoto similarity, all pairs
low high
All pair values as a table (same data as the map)
sharedonly firstonly second

Mapping chemical space

Fingerprints place every molecule in a space with thousands of dimensions. Projecting that space onto two dimensions shows families of related molecules (clusters found with k-means) and where actives sit. Colour by label, potency or cluster, then select a molecule to see its five most similar neighbours.

Neighbour agreement by cluster
Selected molecule

Select a point on the map.

Clusters at the current cutoffs
Cluster size and active share
Map at a glance

Browse every molecule

All curated measurements in one searchable table. Filter by ChEMBL ID or SMILES fragment, by label at your current cutoffs, or by IC50 range. Click a ChEMBL ID to open its assay record. Everything here is real data.

Look up a molecule

Paste a full SMILES for an exact match, or any fragment of a SMILES or ChEMBL ID. Exact matches show the measured IC50, the label at your cutoffs, molecular weight, LogP and a link to ChEMBL.

Type above to search the 3425 curated molecules.

Judging a classifier

A model outputs a probability, and a threshold turns it into active or inactive. Drag the line through the score distributions and every panel responds: the ROC and precision-recall curves, the confusion matrix and the metrics for the three classifiers side by side.

Distribution and matrix for
true activestrue inactives
ROC curve
Precision-recall curve
Confusion matrix
flagged active
flagged inactive
actually active
found
missed
actually inactive
false alarm
correct
precision
recall
F1
All three models at this threshold

Can you trust the probabilities?

A reliability diagram checks whether a predicted 80% really means 80% of those molecules are active. Points below the diagonal mean the model is overconfident; above it, underconfident. Choose the number of bins and which models to compare.

Reliability diagram
Predictions per bin
Calibration error

ECE is the average gap between predicted and observed rates. Brier score is the mean squared error of the probabilities; lower is better for both.

Learning curve: does more data help?
At 100% of training data

Solid lines are validation scores with a band for fold-to-fold spread; dashed lines are training scores. A persistent gap suggests overfitting, and a flat validation curve suggests more data will add little. Dots on the right are five cross-validation folds.

Where the models go wrong

Aggregate scores hide which molecules a model gets wrong. At the chosen threshold, these are the held-out compounds that each model misjudges most confidently, and a count of how many models agree on each molecule.

Model for the tables
How many models classify each test molecule correctly
Most confident misses: actives scored low
Most confident false alarms: inactives scored high

Every test prediction

All held-out test molecules with the true label and each model's probability of being active. Search by ID or SMILES, filter by whether the models get it right at the threshold below, and sort by disagreement or confidence. Scores are real held-out predictions.

Why accuracy can mislead

Active molecules are usually rare. A model that calls everything inactive can look accurate while finding nothing, and even a good model produces mostly false alarms when actives are scarce. Change the dataset balance and the model's strengths to see how accuracy, precision and F1 diverge.

accuracy, always inactiveaccuracy, modelprecision, modelF1, model
Metric against share of actives
Outcomes at current settings, per 100 — confusion breakdown

Every 100 molecules, scored by the model

What drives a prediction

Attribution methods such as SHAP split a prediction into contributions from each fingerprint bit. Each dot is one molecule: its position shows how much the bit pushed the prediction up (right) or down (left), and its colour shows whether the bit was present. Bars on the right rank bits by average impact.

bit presentbit absent

Three classifiers, one benchmark

Random Forest

Many decision trees voting together. A sturdy baseline for fingerprint data.

XGBoost

Gradient-boosted trees that correct earlier errors one round at a time.

LightGBM

A fast gradient-boosting variant built for high-dimensional features.

Tuning the models

Tree-based models have settings that trade flexibility against overfitting. A grid search scores every combination with cross-validation. Each cell shows mean AUC-ROC; the ringed cell is the best. Select a cell for its spread across folds.

Selected setting

Do the models generalise to new scaffolds?

A random split puts near-identical molecules on both sides of the train and test line, which can flatter a model. A scaffold split keeps every ring scaffold on one side only, so the test molecules are chemically new. The gap between the two bars is how much of the score depends on having seen close relatives.

random splitscaffold split
Most common ring scaffolds
activeinactive

Limits worth knowing

Noisy labels

IC50 values come from different labs and assay set-ups, so two measurements of the same molecule can disagree. Averaging duplicates helps but does not remove this.

Easy splits flatter models

A random train and test split puts near-identical molecules on both sides. Splitting by chemical scaffold is a stricter test of how well a model handles new chemistry.

Active in a test is not a drug

A strong IC50 shows a molecule blocks the enzyme in an assay. Safety, absorption and effect in cells still need separate work.

Questions and answers

What is IC50?

The concentration of a compound needed to cut an enzyme's activity by half. A smaller IC50 means a more potent inhibitor.

What is the 3C-like protease?

An enzyme (also called the main protease) that SARS-CoV-2 uses to cut its long protein chain into functional pieces. Blocking it stops the virus from replicating.

What is a SMILES string?

A line of text that writes a molecule's structure, such as CCO for ethanol. RDKit reads SMILES and turns them into molecules.

What do AUC-ROC and F1 measure?

AUC-ROC measures how well a model ranks actives above inactives across every threshold. F1 combines precision and recall at one chosen threshold.

Why drop molecules with in-between IC50 values?

Molecules near the boundary are hard to call either way. Removing them gives cleaner labels for training, at the cost of a smaller dataset.

Run it yourself

Try the app right here

The live Streamlit app, embedded. Change cutoffs, train the models and score your own molecule — or open it full-screen.

Inside the notebook

  1. Retrieve ChEMBL data
  2. Clean and label
  3. Property comparison
  4. Fingerprints
  5. Train and evaluate
  6. Important bits
  7. Score a molecule

Tools

  • ChEMBL
  • RDKit
  • Scikit-learn
  • XGBoost
  • LightGBM
  • Seaborn
  • Streamlit

Contact me

Questions about the analysis, the data or the models? Reach me at harshinisuresha7@gmail.com.