This tool has been financed by Project PID2023-148309OA-I00 funded by MICIU/AEI/10.13039/501100011033 and by ERDF, EU.
Ersilia's LazyQSAR
A Python library for building supervised QSAR models quickly, with minimal configuration. LazyQSAR automates chemical descriptor computation, and model selection to produce robust models for property and activity prediction.
Two entry points:
LazyClassifierQSAR: pass SMILES strings directly; built-in descriptors are computed automaticallyLazyClassifier: bring your own pre-computed descriptor arrays
Table of Contents
- Installation
- Python API
- CLI
- Running the tests
- How It Works
- Base Models
- Ersilia Model Hub integration
- Disclaimer
Installation
We recommend installation from source:
git clone https://github.com/ersilia-os/lazy-qsar.git
cd lazy-qsar
pip install -e .
The base install includes only lightweight runtime dependencies (numpy, onnxruntime, etc.), sufficient for loading and running pre-trained ONNX models without any ML and chemistry-related packages (RDKit). Therefore, the base install assumes descriptors are provided by the user.
You can install optional extras depending on your use case:
| Extra | Command | Adds |
|---|---|---|
fit |
pip install -e ".[fit]" |
Training dependencies (scikit-learn, XGBoost, scipy, ONNX conversion tools) |
descriptors |
pip install -e ".[descriptors]" |
Built-in molecular descriptors (RDKit, FPSim2, deep-learning models) |
all |
pip install -e ".[all]" |
Everything above |
CPU-only deployments where pip would otherwise pull the CUDA torch wheel (~3 GB) can pass
--cpu-torchto force-reinstall torch from PyTorch's CPU index:lazyqsar setup --descriptors --cpu-torch.
The first time you use deep-learning descriptors (Chemeleon, CLAMP, CDDD), their checkpoints are downloaded automatically. To do this in advance:
lazyqsar setup --descriptors
Python API
LazyClassifierQSAR (SMILES)
Pass SMILES strings directly. Choose a descriptor mode:
| Mode | Descriptors | Notes |
|---|---|---|
fast |
Morgan fingerprints | No deep-learning models, fastest |
slow |
CDDD, Chemeleon, CLAMP, Morgan, RDKit | Most thorough |
from lazyqsar.qsar import LazyClassifierQSAR
model = LazyClassifierQSAR(mode="slow") # default is "slow"
model.fit(smiles_list=smiles_train, y=y_train)
ranks = model.predict_rank(smiles_list=smiles_test)[:, 1] # percentile within the model's own training distribution
Other prediction methods are predict_proba, predict_logit, predict_score, predict_lift and predict (binary labels). All six share one implementation with the CLI, so a checkpoint gives the same answer through either entry point.
predict_rankis a percentile against the training distribution of that model, so ranks are not comparable between models and compress on chemistry unlike the training set. Usepredict_probawhen you need a calibrated value. Within one modelrankis a monotone view ofproba: they order molecules identically, so any ordering-only metric (AUROC, AUPRC, BEDROC) gives the same answer from either.
LazyClassifier (custom descriptors)
Pass your own descriptor arrays or HDF5 files. We recommend the Ersilia Model Hub for descriptor computation — its .h5 output format is supported natively.
from lazyqsar.agnostic import LazyClassifier
# From a NumPy array
model = LazyClassifier()
model.fit(X=X_train, y=y_train)
y_hat = model.predict_proba(X=X_test)[:, 1]
# From an Ersilia .h5 file
model.fit(h5_file="descriptors.h5", y=y_train)
y_hat = model.predict_proba(h5_file="descriptors.h5")[:, 1]
The same prediction methods listed above are available, using X= instead of smiles_list=.
Saving and loading
Models are saved as ONNX files, so inference only requires numpy and onnxruntime, i.e. no scikit-learn or XGBoost at prediction time. Metadata is stored in JSON format.
To save models:
model.save(model_dir) # directory
model.save("my_model.zip") # or zip archive
And to load them:
model = LazyClassifierQSAR.load(model_dir)
y_hat = model.predict_proba(smiles_list=smiles_test)[:, 1]
model = LazyClassifier.load(model_dir)
y_hat = model.predict_proba(X=X_test)[:, 1]
For multi-endpoint prediction across multiple model directories, see Ersilia Model Hub integration.
CLI
All commands are available through the lazyqsar entry point.
Fit:
The --input directory must contain one CSV per task, with SMILES in the first column and binary labels (0/1) in the second column, with a header row.
lazyqsar fit --task classification --input $DATA_DIR --output $MODEL_DIR --mode slow
Pass --models_txt to train a subset of tasks (one CSV stem per line); without it, all CSVs in the directory are used.
Predict:
lazyqsar predict --input $INPUT_CSV --model $MODEL_DIR --output $OUTPUT_CSV [--models_txt FILE] [--predict_type TYPE]
The output CSV contains one column per task, ordered alphabetically by task name, or filtered and ordered by --models_txt at predict time. --predict_type controls the output format:
| type | meaning |
|---|---|
proba (default) |
calibrated probability of the positive class |
rank |
percentile within the model's own training distribution |
logit |
log-odds of the calibrated probability |
lift |
probability divided by the training-set positive rate |
score |
the pre-calibration scale, read off the calibrated probability |
binary |
0/1 label, thresholded at probability 0.5 |
All six rank molecules identically — they are different scales on one quantity, so sorting
by any of them gives the same order. score reports what the model looked like before
calibration; it reaches that scale through a monotone map stored in the checkpoint rather
than by pooling the raw per-descriptor scores, which would not agree with proba about the
order. Checkpoints fitted before v3.5.0 carry no map and keep the older behaviour, where
score could disagree.
How it works
LazyQSAR builds an ensemble for each descriptor set through four steps:
-
Portfolio selection: the dataset is profiled (sample count, dimensionality, sparsity, class imbalance) and a rule-based selector decides which heads to train. The default portfolio is XGBoost + Random Forest; Linear Models and Support Vector Machines are added automatically for small, high-dimensional, or low-prevalence datasets.
-
Preprocessing: a scaler (
StandardScaler,RobustScaler,MaxAbsScaler, orPowerTransformer) and an optional correlation-based feature reducer are selected automatically from dataset statistics. -
Heads: each selected head is fitted on preprocessed features. For severely imbalanced datasets, balanced sub-batches are used and the batch predictions are averaged.
-
Pooling: head predictions are combined via a learned gating network (
InnerClassifierPooler). When usingLazyClassifierQSAR, a separate ensemble is trained per descriptor type and their predictions are combined via an AUC-weighted ensemble that accounts for per-sample prediction confidence.
The full pipeline is exported to ONNX, so inference requires only numpy and onnxruntime.
Base Models
The components under lazyqsar/base/ can be used independently of the full pipeline:
| Module | Description |
|---|---|
lazyqsar.base.preprocessing |
Automatic scaler and feature reducer selection |
lazyqsar.base.xgboost |
Automatic XGBoost hyperparameter selection with portfolio comparison |
lazyqsar.base.linear |
Automatic linear model selection (logistic/ridge/SGD) |
lazyqsar.base.randomforest |
Random Forest classifier with zero-shot hyperparameter selection |
lazyqsar.base.svc |
Support Vector Classifier with automatic kernel and C selection |
Running the tests
pip install -e ".[fit,test]"
pytest
The suite is organised by what a test needs installed, so it runs — and reports honestly — in whichever environment you have. A test whose tier is missing is skipped with a message naming the absent module, never an error.
| Selection | Needs | Covers |
|---|---|---|
pytest -m "not fit and not chem" |
the base install | the inference path, the ensemble arithmetic, the registry and the CLI surface |
pytest |
.[fit] plus rdkit |
the above, plus fitting, ONNX export, the pipeline, and Morgan and RDKit descriptors |
Those are the only two selections, and CI runs both as parallel jobs on Python 3.12, so a
pull request finishes in a few minutes. There is no third, larger one: pip install ".[all]" pulls in torch, chemprop and chemeleon but adds no tests, because nothing in the
suite executes them.
The base selection is the contract Ersilia Model Hub templates depend on: it must pass on
the core dependencies alone — numpy, onnxruntime, pandas, h5py, psutil, rich
and loguru — with no scikit-learn, xgboost or RDKit anywhere on the path. Add
-n auto --dist loadfile for the heavier tier, and --durations=15 to see where the time
goes.
What is not covered
Worth stating plainly, because a green suite does not mean these are exercised:
- Three of the five descriptors have no execution coverage. Only
morganandrdkitare ever actually run.cddd,clampandchemeleonappear in tests only as names — in stubbed registries, CLI argument validation and import-purity checks — so a change to any of them can breakslowmode without reddening anything. Test them by hand against real molecules before changing them. - The SVC ONNX export. The score-column sign and the per-head export-versus-fit comparison are not checked. A converter regression there is silent.
- Python 3.11 and 3.13. Supported per
requires-python, exercised by nothing.
Install the fit extra from the pins rather than whatever is already in your environment:
scikit-learn in particular is pinned to 1.9.1, and descriptor selection differs enough
between minor versions to change which descriptors a portfolio keeps.
Test data lives in tests/data/ and is documented in tests/data/README.md.
Ersilia Model Hub integration
LazyQSAR models can be used inside an Ersilia Model Hub template. See eos3dys for an example.
Basically, lazyqsar fit can be used to produce a checkpoints folder with one sub-directory per task and per descriptor type:
checkpoints/
└── task1/
├── cddd/
│ ├── featurizer.json
│ ├── metadata.json
│ └── batch_0/
│ ├── preprocessor.onnx
│ ├── xgboost.onnx
│ └── pooler.json
├── chemeleon/ (same structure)
├── clamp/ (same structure)
├── morgan/ (same structure)
└── rdkit/ (same structure)
The code/main.py inference script:
import os, sys
from lazyqsar.api.classifier_predict import predict
from ersilia_pack_utils.core import read_smiles, write_out
root = os.path.dirname(os.path.abspath(__file__))
checkpoints_dir = os.path.abspath(os.path.join(root, "..", "checkpoints"))
_, smiles_list = read_smiles(input_file)
outputs, header = predict(model_dir=checkpoints_dir, smiles=smiles_list, predict_type="rank")
write_out(outputs, header, output_file, np.float32)
Descriptors are computed once per descriptor type and shared across every task, and molecules are scored in blocks whose working set is released before the next block starts, so peak memory does not grow with the size of the input. The block size is derived from a fixed working-set budget of roughly 1 GB and the number of endpoints being scored, and is always a whole number of chunks — which is what keeps the output identical however the input is divided — so there is no knob for it. LAZYQSAR_PREDICT_CHUNK (default 1000) sets the featurization and inference batch size within a block.
model_dir also accepts a dict[str, str] mapping column names to model directories, for scoring multiple targets stored under separate paths. Column names and their order are preserved exactly as given.
Disclaimer
This library is intended for quick QSAR modeling. For a more complete automated QSAR pipeline, refer to ZairaChem.
ZairaChem's version, with an earlier version of LazyQSAR, was presented in this article:
@article{Turon2023,
author = {Turon, G. and Hlozek, J. and Woodland, J.G. and et al.},
title = {First fully-automated AI/ML virtual screening cascade implemented at a drug discovery centre in Africa},
journal = {Nat Commun},
volume = {14},
pages = {5736},
year = {2023},
doi = {10.1038/s41467-023-41512-2},
url = {https://doi.org/10.1038/s41467-023-41512-2}
}
About the Ersilia Open Source Initiative
The Ersilia Open Source Initiative is a tech non-profit organization with the mission to equip laboratories, universities, and clinics in the Global South with AI/ML tools for infectious disease research. We work on the principles of open science, decolonized research, and egalitarian access to knowledge and research outputs. You can support Ersilia by clicking here.
Metadata
Release files for lazyqsar 3.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| lazyqsar-3.5.0.tar.gz | 191.4 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lazyqsar-3.5.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 417.3 kB
Release files / lazyqsar-3.5.0.tar.gz
| Download URL | lazyqsar-3.5.0.tar.gz |
|---|---|
| Size | 191.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2dec3b44c7c768fc98fd35dd30d2b12f01137d31ba32444fd92ed8221babff13
|
|
BLAKE2b-256 checksum How to use checksums |
b6e39b96bdae82d7210428d3d00ef7f0670f1f63fd89083eb991ae81b95a7d8f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency logRelease files / lazyqsar-3.5.0-py3-none-any.whl
| Download URL | lazyqsar-3.5.0-py3-none-any.whl |
|---|---|
| Size | 225.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d35c2fe87fd73ef8fd26c2f409b7a127c14d7ad81cf0f4aa9ec5c52a14f9230d
|
|
BLAKE2b-256 checksum How to use checksums |
7a86f7a6a270e37042eb984c998bb8cb061bbb4a695ebb46e7c5567d9062e67e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency logSecurity for lazyqsar 3.5.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for lazyqsar-3.5.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.