How to Perform Data Curation and Classify Bioactivity Data on ChEMBL Database
A classifier does not learn activity. It learns the labels you attached to the molecules.
ChEMBL will give you the measurements. It will not give you the labels. For one target the download has many rows, often several for the same compound, some with no number, and a continuous IC50 that is not yet a class. This note turns the human acetylcholinesterase table into a file a classifier can train on: one row per molecule, a mean potency, and a class that leaves the uncertain middle out.
What the download actually contains
ChEMBL is a curated collection of bioactive, drug-like molecules and the measurements reported for them. Two counts on the release notes are easy to mix up. Compound records count depositions. Unique compounds are fewer, because the same structure can arrive from more than one source.
The ChEMBL 30 release, prepared on 22 February 2022, contained 2,786,911 compound records, 2,157,379 compounds, 1,458,215 assays, and 14,855 targets [1]. ChEMBL 33, prepared on 31 May 2023, contained 3,051,613 compound records, 2,399,743 compounds, 1,610,596 assays, and 15,398 targets [2]. A later paper describes the same platform and the kinds of data it holds [3]. Use the current release notes if you need today’s size. The curation steps below do not depend on it.
One target, one activity type
Search the target name, not a disease and not a compound, when the model is meant to describe that protein. Acetylcholinesterase returns many targets. The one used here is the human protein, CHEMBL220. Pick the organism on purpose. A homolog is a different label.
On the target report, the activity chart shows how those measurements were reported. In the interface used for this tutorial, IC50 was the common type, listed 8,205 times. That count grows as new papers are added. Treat it as a snapshot of the download, not as a property of the enzyme.
Download the IC50 slice as CSV or TSV. Then keep four columns, and delete the rest before you open the file in a notebook:
Molecule ChEMBL IDSmilesStandard TypeStandard Value
ChEMBL standard IC50 values are in nanomolar. If a later export renames a column, rename it back to this list. The notebook expects these headers, and it expects Standard Value to be a number.
One row per molecule
The same ChEMBL ID often appears more than once. Laboratories did not run the same assay. The values disagree, and both rows are “the” measurement until you decide on a rule.
The rule in this tutorial is a mean of Standard Value on the linear nanomolar scale, grouped by ChEMBL ID. That is a teaching choice. A factor of ten in potency is not symmetric on a linear scale, and a mean mixes assays that were not run under the same conditions. It is enough to build a labelled practice set. It is not assay-aware curation.
After the mean is attached, keep the first row for each ID and drop rows that still have a missing value. Averaging first matters: the mean has to see every replicate before the extras are deleted.
import pandas as pd
columns = ["Molecule ChEMBL ID", "Smiles", "Standard Type", "Standard Value"]
x = pd.read_csv("ache.csv")
x = x[columns]
x["Standard Value"] = pd.to_numeric(x["Standard Value"], errors="coerce")
x["mean_value"] = x.groupby("Molecule ChEMBL ID")["Standard Value"].transform("mean")
x = x.drop_duplicates("Molecule ChEMBL ID", keep="first")
x = x.dropna()
to_numeric(..., errors="coerce") turns anything that is not a number into a missing value, so a footnote or a blank cell cannot silently become text and break the mean. Restricting the frame to the four columns first stops dropna from rejecting a molecule because some unused field was empty.
The class is a gap, not a line
Potent enzyme inhibitors in this literature often sit in the nanomolar range. For a classification exercise, call a molecule active when its mean IC50 is at most 1000 nM, which is 1 µM. Call it inactive when the mean is above 10000 nM, which is 10 µM. Everything between those two cuts is left out of the file.
active = x.loc[x["mean_value"] <= 1000].copy()
inactive = x.loc[x["mean_value"] > 10000].copy()
active["Class"] = 1
inactive["Class"] = 0
combined = pd.concat([active, inactive], axis=0)
combined.to_csv("ache_labelled.csv", index=False)
.copy() matters. Assigning Class on a sliced frame is how pandas loses the new column or warns that it might. The saved file contains only the two tails. It is smaller than the curated table, and it is enriched for molecules whose mean fell clearly on one side.
What this file is
The CSV is a practice set for one target and one endpoint. A few limits travel with it.
- The class is a cutoff on a mean, not a second experiment.
- Replicates were averaged on a linear scale, across assays that were not matched.
- Molecules between 1 µM and 10 µM are absent, so a model never has to make the close call.
- The same cuts on a different target, or on Ki rather than IC50, would be a different labelling decision. Do that in a separate file.
Those choices are the model, as much as the classifier you train afterwards.
References
[1] ChEMBL 30 release note, 10 March 2022. Data prepared 22 February 2022. https://chembl.blogspot.com/2022/03/chembl-30-released.html
[2] ChEMBL 33 release note, 13 June 2023. Data prepared 31 May 2023. https://chembl.blogspot.com/2023/06/release-of-chembl-33.html
[3] Zdrazil, B. et al. The ChEMBL Database in 2023: a drug discovery platform spanning multiple bioactivity data types and time periods. Nucleic Acids Research, 2024, 52, D1180–D1192. https://doi.org/10.1093/nar/gkad1004