A molecule can look like a promising hit and still be lying to you.
This is the problem PAINS filters were built to catch.
PAINS stands for Pan-Assay Interference Compounds — structures that show "activity" across many unrelated biological assays, not because they bind a target, but because of artifacts like aggregation, redox cycling, metal chelation, or fluorescence interference. They look like hits in the data. They are not real hits in the biology.
Baell and Holloway flagged this in 2010 after analyzing thousands of HTS screening results. They identified around 400 substructure patterns that kept showing up as false positives across completely different assay types. That list became the basis for the PAINS filters used today.
RDKit has this built in through FilterCatalog:
from rdkit import Chem
from rdkit.Chem.FilterCatalog import FilterCatalog, FilterCatalogParams
params = FilterCatalogParams()
params.AddCatalog(FilterCatalogParams.FilterCatalogs.PAINS)
catalog = FilterCatalog(params)
mol = Chem.MolFromSmiles("O=C1C=CC(=O)C=C1") # example: a quinone, a classic PAINS-flagged motif
entry = catalog.GetFirstMatch(mol)
if entry:
print(entry.GetDescription())
A few things worth knowing before you use this in a real workflow:
→ PAINS filters are a triage step, not a verdict. A flagged compound needs follow-up: orthogonal assays, dose-response curves, counter-screens. Dismissing it outright on a substructure match alone is its own kind of error.
→ Some approved drugs contain PAINS-flagged substructures. The filter tells you "investigate further," not "discard."
→ This is exactly why ADMET and HTS pipelines run PAINS as one filter among several, not as a single pass/fail gate.
If you are building any kind of virtual screening or hit triage pipeline, this is one of the cheapest checks you can run before committing wet-lab time to a compound.
Comments
Post a Comment