qsarkit-learn 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qsarkit_learn-0.5.0/CONTRIBUTING_STYLE.md +182 -0
- qsarkit_learn-0.5.0/LICENSE +21 -0
- qsarkit_learn-0.5.0/MANIFEST.in +26 -0
- qsarkit_learn-0.5.0/PKG-INFO +408 -0
- qsarkit_learn-0.5.0/README.md +323 -0
- qsarkit_learn-0.5.0/notebooks/01_curation_and_the_functional_api.ipynb +682 -0
- qsarkit_learn-0.5.0/notebooks/02_representation_and_chemical_space.ipynb +1861 -0
- qsarkit_learn-0.5.0/notebooks/03_modeling_validation_and_applicability.ipynb +2181 -0
- qsarkit_learn-0.5.0/notebooks/04_sar_explainability_and_reporting.ipynb +3770 -0
- qsarkit_learn-0.5.0/notebooks/05_classification_calibration_and_deployment.ipynb +7337 -0
- qsarkit_learn-0.5.0/notebooks/README.md +59 -0
- qsarkit_learn-0.5.0/package.md +726 -0
- qsarkit_learn-0.5.0/pyproject.toml +197 -0
- qsarkit_learn-0.5.0/qsarkit/__init__.py +134 -0
- qsarkit_learn-0.5.0/qsarkit/applicability/__init__.py +57 -0
- qsarkit_learn-0.5.0/qsarkit/applicability/_analyzer.py +317 -0
- qsarkit_learn-0.5.0/qsarkit/applicability/_domains.py +1010 -0
- qsarkit_learn-0.5.0/qsarkit/base/__init__.py +29 -0
- qsarkit_learn-0.5.0/qsarkit/base/exceptions.py +67 -0
- qsarkit_learn-0.5.0/qsarkit/base/optional_deps.py +76 -0
- qsarkit_learn-0.5.0/qsarkit/base/transformer.py +215 -0
- qsarkit_learn-0.5.0/qsarkit/chemistry/__init__.py +45 -0
- qsarkit_learn-0.5.0/qsarkit/chemistry/fragments/__init__.py +6 -0
- qsarkit_learn-0.5.0/qsarkit/chemistry/fragments/_core_extractor.py +111 -0
- qsarkit_learn-0.5.0/qsarkit/chemistry/fragments/_remover.py +133 -0
- qsarkit_learn-0.5.0/qsarkit/chemistry/glycans/__init__.py +7 -0
- qsarkit_learn-0.5.0/qsarkit/chemistry/glycans/_descriptors.py +109 -0
- qsarkit_learn-0.5.0/qsarkit/chemistry/glycans/_detector.py +194 -0
- qsarkit_learn-0.5.0/qsarkit/chemistry/glycans/_remover.py +139 -0
- qsarkit_learn-0.5.0/qsarkit/chemistry/graph/__init__.py +5 -0
- qsarkit_learn-0.5.0/qsarkit/chemistry/graph/_graph.py +129 -0
- qsarkit_learn-0.5.0/qsarkit/chemistry/standardization/__init__.py +5 -0
- qsarkit_learn-0.5.0/qsarkit/chemistry/standardization/_standardizer.py +143 -0
- qsarkit_learn-0.5.0/qsarkit/chemspace/__init__.py +41 -0
- qsarkit_learn-0.5.0/qsarkit/chemspace/_analyzers.py +996 -0
- qsarkit_learn-0.5.0/qsarkit/chemspace/_fingerprints.py +214 -0
- qsarkit_learn-0.5.0/qsarkit/cluster/__init__.py +21 -0
- qsarkit_learn-0.5.0/qsarkit/cluster/_butina.py +212 -0
- qsarkit_learn-0.5.0/qsarkit/cluster/_pickers.py +384 -0
- qsarkit_learn-0.5.0/qsarkit/data_quality/__init__.py +53 -0
- qsarkit_learn-0.5.0/qsarkit/data_quality/_duplicates.py +398 -0
- qsarkit_learn-0.5.0/qsarkit/data_quality/_pipeline.py +370 -0
- qsarkit_learn-0.5.0/qsarkit/data_quality/_validators.py +600 -0
- qsarkit_learn-0.5.0/qsarkit/explainability/__init__.py +80 -0
- qsarkit_learn-0.5.0/qsarkit/explainability/_atom_maps.py +561 -0
- qsarkit_learn-0.5.0/qsarkit/explainability/_atomic.py +545 -0
- qsarkit_learn-0.5.0/qsarkit/explainability/_importance.py +672 -0
- qsarkit_learn-0.5.0/qsarkit/feature_selection/__init__.py +38 -0
- qsarkit_learn-0.5.0/qsarkit/feature_selection/_boruta.py +225 -0
- qsarkit_learn-0.5.0/qsarkit/feature_selection/_correlation.py +194 -0
- qsarkit_learn-0.5.0/qsarkit/feature_selection/_mutual_info.py +155 -0
- qsarkit_learn-0.5.0/qsarkit/feature_selection/_rfe.py +133 -0
- qsarkit_learn-0.5.0/qsarkit/feature_selection/_variance.py +97 -0
- qsarkit_learn-0.5.0/qsarkit/functional/__init__.py +174 -0
- qsarkit_learn-0.5.0/qsarkit/functional/_core.py +1127 -0
- qsarkit_learn-0.5.0/qsarkit/functional/_model_steps.py +1310 -0
- qsarkit_learn-0.5.0/qsarkit/functional/_steps.py +999 -0
- qsarkit_learn-0.5.0/qsarkit/functional/_viz.py +546 -0
- qsarkit_learn-0.5.0/qsarkit/metrics/__init__.py +123 -0
- qsarkit_learn-0.5.0/qsarkit/metrics/_calibration.py +605 -0
- qsarkit_learn-0.5.0/qsarkit/metrics/_classification.py +748 -0
- qsarkit_learn-0.5.0/qsarkit/metrics/_common.py +79 -0
- qsarkit_learn-0.5.0/qsarkit/metrics/_regression.py +949 -0
- qsarkit_learn-0.5.0/qsarkit/metrics/_reports.py +167 -0
- qsarkit_learn-0.5.0/qsarkit/metrics/_thresholds.py +528 -0
- qsarkit_learn-0.5.0/qsarkit/model_selection/__init__.py +55 -0
- qsarkit_learn-0.5.0/qsarkit/model_selection/_search.py +242 -0
- qsarkit_learn-0.5.0/qsarkit/model_selection/_splitters.py +802 -0
- qsarkit_learn-0.5.0/qsarkit/models/__init__.py +33 -0
- qsarkit_learn-0.5.0/qsarkit/models/_baseline.py +198 -0
- qsarkit_learn-0.5.0/qsarkit/models/_consensus.py +249 -0
- qsarkit_learn-0.5.0/qsarkit/models/_facades.py +830 -0
- qsarkit_learn-0.5.0/qsarkit/models/_gaussian_process.py +164 -0
- qsarkit_learn-0.5.0/qsarkit/models/_neural_network.py +112 -0
- qsarkit_learn-0.5.0/qsarkit/models/_pls.py +128 -0
- qsarkit_learn-0.5.0/qsarkit/models/_random_forest.py +102 -0
- qsarkit_learn-0.5.0/qsarkit/models/_svm.py +82 -0
- qsarkit_learn-0.5.0/qsarkit/models/_tanimoto_kernel.py +187 -0
- qsarkit_learn-0.5.0/qsarkit/neighbors/__init__.py +25 -0
- qsarkit_learn-0.5.0/qsarkit/neighbors/_distance.py +212 -0
- qsarkit_learn-0.5.0/qsarkit/neighbors/_knn.py +306 -0
- qsarkit_learn-0.5.0/qsarkit/neighbors/_search.py +236 -0
- qsarkit_learn-0.5.0/qsarkit/persistence/__init__.py +61 -0
- qsarkit_learn-0.5.0/qsarkit/persistence/_bundle.py +735 -0
- qsarkit_learn-0.5.0/qsarkit/persistence/_metadata.py +187 -0
- qsarkit_learn-0.5.0/qsarkit/py.typed +0 -0
- qsarkit_learn-0.5.0/qsarkit/reporting/__init__.py +66 -0
- qsarkit_learn-0.5.0/qsarkit/reporting/_plots.py +979 -0
- qsarkit_learn-0.5.0/qsarkit/reporting/_report.py +1015 -0
- qsarkit_learn-0.5.0/qsarkit/representation/__init__.py +65 -0
- qsarkit_learn-0.5.0/qsarkit/representation/descriptors/_3d.py +162 -0
- qsarkit_learn-0.5.0/qsarkit/representation/descriptors/__init__.py +37 -0
- qsarkit_learn-0.5.0/qsarkit/representation/descriptors/_base.py +125 -0
- qsarkit_learn-0.5.0/qsarkit/representation/descriptors/_calculator.py +169 -0
- qsarkit_learn-0.5.0/qsarkit/representation/descriptors/_constitutional.py +185 -0
- qsarkit_learn-0.5.0/qsarkit/representation/descriptors/_fragments.py +78 -0
- qsarkit_learn-0.5.0/qsarkit/representation/descriptors/_lipinski.py +139 -0
- qsarkit_learn-0.5.0/qsarkit/representation/descriptors/_physicochemical.py +127 -0
- qsarkit_learn-0.5.0/qsarkit/representation/descriptors/_rdkit_descriptors.py +78 -0
- qsarkit_learn-0.5.0/qsarkit/representation/embeddings/__init__.py +16 -0
- qsarkit_learn-0.5.0/qsarkit/representation/embeddings/_chemberta.py +89 -0
- qsarkit_learn-0.5.0/qsarkit/representation/embeddings/_hf_base.py +187 -0
- qsarkit_learn-0.5.0/qsarkit/representation/fingerprints/__init__.py +59 -0
- qsarkit_learn-0.5.0/qsarkit/representation/fingerprints/_atompair.py +187 -0
- qsarkit_learn-0.5.0/qsarkit/representation/fingerprints/_avalon.py +110 -0
- qsarkit_learn-0.5.0/qsarkit/representation/fingerprints/_base.py +150 -0
- qsarkit_learn-0.5.0/qsarkit/representation/fingerprints/_combiner.py +160 -0
- qsarkit_learn-0.5.0/qsarkit/representation/fingerprints/_maccs.py +80 -0
- qsarkit_learn-0.5.0/qsarkit/representation/fingerprints/_mhfp.py +454 -0
- qsarkit_learn-0.5.0/qsarkit/representation/fingerprints/_morgan.py +180 -0
- qsarkit_learn-0.5.0/qsarkit/representation/fingerprints/_pharmacophore.py +85 -0
- qsarkit_learn-0.5.0/qsarkit/representation/fingerprints/_rdkit.py +232 -0
- qsarkit_learn-0.5.0/qsarkit/representation/mol2vec/__init__.py +14 -0
- qsarkit_learn-0.5.0/qsarkit/representation/mol2vec/_mol2vec.py +365 -0
- qsarkit_learn-0.5.0/qsarkit/sar/__init__.py +45 -0
- qsarkit_learn-0.5.0/qsarkit/sar/_cliffs.py +925 -0
- qsarkit_learn-0.5.0/qsarkit/sar/_mmp.py +372 -0
- qsarkit_learn-0.5.0/qsarkit/sar/_rgroup.py +490 -0
- qsarkit_learn-0.5.0/qsarkit/transform/__init__.py +49 -0
- qsarkit_learn-0.5.0/qsarkit/transform/_transforms.py +722 -0
- qsarkit_learn-0.5.0/qsarkit/uncertainty/__init__.py +70 -0
- qsarkit_learn-0.5.0/qsarkit/uncertainty/_calibration.py +308 -0
- qsarkit_learn-0.5.0/qsarkit/uncertainty/_conformal.py +536 -0
- qsarkit_learn-0.5.0/qsarkit/uncertainty/_estimators.py +554 -0
- qsarkit_learn-0.5.0/qsarkit/utils/__init__.py +49 -0
- qsarkit_learn-0.5.0/qsarkit/utils/constants.py +82 -0
- qsarkit_learn-0.5.0/qsarkit/utils/io.py +624 -0
- qsarkit_learn-0.5.0/qsarkit/utils/logging.py +134 -0
- qsarkit_learn-0.5.0/qsarkit/utils/validation.py +360 -0
- qsarkit_learn-0.5.0/qsarkit/validation/__init__.py +43 -0
- qsarkit_learn-0.5.0/qsarkit/validation/_cross_validation.py +257 -0
- qsarkit_learn-0.5.0/qsarkit/validation/_robustness.py +609 -0
- qsarkit_learn-0.5.0/qsarkit/validation/_scoring.py +318 -0
- qsarkit_learn-0.5.0/qsarkit_learn.egg-info/PKG-INFO +408 -0
- qsarkit_learn-0.5.0/qsarkit_learn.egg-info/SOURCES.txt +225 -0
- qsarkit_learn-0.5.0/qsarkit_learn.egg-info/dependency_links.txt +1 -0
- qsarkit_learn-0.5.0/qsarkit_learn.egg-info/requires.txt +67 -0
- qsarkit_learn-0.5.0/qsarkit_learn.egg-info/top_level.txt +1 -0
- qsarkit_learn-0.5.0/setup.cfg +4 -0
- qsarkit_learn-0.5.0/tests/api/test_estimator_contract.py +207 -0
- qsarkit_learn-0.5.0/tests/applicability/test_domains.py +336 -0
- qsarkit_learn-0.5.0/tests/chemistry/test_fragments.py +76 -0
- qsarkit_learn-0.5.0/tests/chemistry/test_glycans.py +75 -0
- qsarkit_learn-0.5.0/tests/chemistry/test_standardization.py +78 -0
- qsarkit_learn-0.5.0/tests/chemspace/test_analyzers.py +206 -0
- qsarkit_learn-0.5.0/tests/chemspace/test_chemspace.py +101 -0
- qsarkit_learn-0.5.0/tests/cluster/test_cluster.py +243 -0
- qsarkit_learn-0.5.0/tests/conftest.py +45 -0
- qsarkit_learn-0.5.0/tests/data_quality/test_data_quality.py +461 -0
- qsarkit_learn-0.5.0/tests/docs/conftest.py +36 -0
- qsarkit_learn-0.5.0/tests/docs/test_doctest_checker.py +85 -0
- qsarkit_learn-0.5.0/tests/docs/test_documentation_examples.py +263 -0
- qsarkit_learn-0.5.0/tests/docs/test_notebooks.py +89 -0
- qsarkit_learn-0.5.0/tests/docs/test_published_site.py +59 -0
- qsarkit_learn-0.5.0/tests/docs/test_readme.py +103 -0
- qsarkit_learn-0.5.0/tests/explainability/test_atom_maps.py +269 -0
- qsarkit_learn-0.5.0/tests/explainability/test_explainability.py +463 -0
- qsarkit_learn-0.5.0/tests/feature_selection/test_feature_selection.py +295 -0
- qsarkit_learn-0.5.0/tests/functional/test_model_steps.py +363 -0
- qsarkit_learn-0.5.0/tests/functional/test_molecules_input.py +104 -0
- qsarkit_learn-0.5.0/tests/functional/test_pipe.py +544 -0
- qsarkit_learn-0.5.0/tests/functional/test_viz.py +162 -0
- qsarkit_learn-0.5.0/tests/metrics/test_calibration.py +298 -0
- qsarkit_learn-0.5.0/tests/metrics/test_classification.py +242 -0
- qsarkit_learn-0.5.0/tests/metrics/test_common.py +47 -0
- qsarkit_learn-0.5.0/tests/metrics/test_regression.py +227 -0
- qsarkit_learn-0.5.0/tests/metrics/test_reports.py +66 -0
- qsarkit_learn-0.5.0/tests/metrics/test_thresholds.py +268 -0
- qsarkit_learn-0.5.0/tests/model_selection/test_splitters.py +362 -0
- qsarkit_learn-0.5.0/tests/models/test_custom_estimators.py +202 -0
- qsarkit_learn-0.5.0/tests/models/test_models.py +465 -0
- qsarkit_learn-0.5.0/tests/neighbors/test_distance.py +128 -0
- qsarkit_learn-0.5.0/tests/neighbors/test_knn.py +264 -0
- qsarkit_learn-0.5.0/tests/persistence/test_persistence.py +437 -0
- qsarkit_learn-0.5.0/tests/reporting/test_reporting.py +488 -0
- qsarkit_learn-0.5.0/tests/representation/test_representation.py +375 -0
- qsarkit_learn-0.5.0/tests/sar/conftest.py +37 -0
- qsarkit_learn-0.5.0/tests/sar/test_cliffs.py +285 -0
- qsarkit_learn-0.5.0/tests/sar/test_mmp.py +125 -0
- qsarkit_learn-0.5.0/tests/sar/test_rgroup.py +200 -0
- qsarkit_learn-0.5.0/tests/transform/test_transforms.py +341 -0
- qsarkit_learn-0.5.0/tests/uncertainty/test_uncertainty.py +470 -0
- qsarkit_learn-0.5.0/tests/utils/test_io_edge_cases.py +306 -0
- qsarkit_learn-0.5.0/tests/utils/test_utils.py +206 -0
- qsarkit_learn-0.5.0/tests/validation/test_cross_validation.py +122 -0
- qsarkit_learn-0.5.0/tests/validation/test_robustness.py +206 -0
- qsarkit_learn-0.5.0/tests/validation/test_scoring.py +244 -0
- qsarkit_learn-0.5.0/typings/rdkit/Avalon.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/AllChem.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/BRICS.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/Crippen.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/Descriptors.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/Draw.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/EnumerateStereoisomers.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/FilterCatalog.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/Fragments.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/GraphDescriptors.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/Lipinski.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/MACCSkeys.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/MolStandardize/__init__.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/MolStandardize/rdMolStandardize.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/Pharm2D.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/QED.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/Recap.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/Scaffolds/MurckoScaffold.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/Scaffolds/__init__.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/__init__.pyi +22 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/inchi.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/rdChemReactions.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/rdDetermineBonds.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/rdDistGeom.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/rdFMCS.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/rdFingerprintGenerator.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/rdForceFieldHelpers.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/rdFreeSASA.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/rdMHFPFingerprint.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/rdMolAlign.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/rdMolDescriptors.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/rdMolTransforms.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/rdPartialCharges.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/rdRGroupDecomposition.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/Chem/rdmolops.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/DataStructs.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/ML/__init__.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/RDLogger.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/SimDivFilters.pyi +14 -0
- qsarkit_learn-0.5.0/typings/rdkit/__init__.pyi +14 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# qsarkit contribution style guide
|
|
2
|
+
|
|
3
|
+
This document defines the conventions every module in this package follows.
|
|
4
|
+
Read it before adding a new module.
|
|
5
|
+
|
|
6
|
+
## 1. Molecule I/O contract
|
|
7
|
+
|
|
8
|
+
Every public class/function that consumes molecules accepts
|
|
9
|
+
`Iterable[rdkit.Chem.Mol]` (never SMILES strings directly as the primary
|
|
10
|
+
input — SMILES parsing belongs in `qsarkit.chemistry` / `qsarkit.utils`
|
|
11
|
+
helpers, or in explicit `from_smiles` convenience constructors).
|
|
12
|
+
|
|
13
|
+
Use `qsarkit.base.ensure_mol_list` to validate/materialize input inside
|
|
14
|
+
`transform`.
|
|
15
|
+
|
|
16
|
+
## 2. Class shape
|
|
17
|
+
|
|
18
|
+
- Mol -> Mol curation/standardization steps subclass
|
|
19
|
+
`qsarkit.base.MoleculeToMoleculeTransformer`.
|
|
20
|
+
- Mol -> features/vectors steps (fingerprints, descriptors, embeddings)
|
|
21
|
+
subclass `qsarkit.base.MoleculeTransformer` or
|
|
22
|
+
`qsarkit.base.FittableMoleculeTransformer` if they must learn parameters
|
|
23
|
+
(e.g. Mol2Vec, a fitted scaler).
|
|
24
|
+
- All such classes implement `fit(mols, y=None)` and `_transform(mols)`
|
|
25
|
+
(the public `transform` is provided by the base class and does
|
|
26
|
+
validation). They are scikit-learn compatible (`BaseEstimator`,
|
|
27
|
+
`TransformerMixin`), so `get_params`/`set_params`/`fit_transform` work
|
|
28
|
+
out of the box — do not override `__init__` in a way that stores
|
|
29
|
+
anything other than the constructor arguments verbatim (sklearn
|
|
30
|
+
convention).
|
|
31
|
+
- Data records (e.g. `ActivityRecord`, `Relation`) are `@dataclass`.
|
|
32
|
+
- API clients (`qsarkit.databases.*`) are plain classes (not transformers)
|
|
33
|
+
with explicit methods per the spec (e.g. `get_by_cid`), using `requests`
|
|
34
|
+
with a `timeout`, raising `qsarkit.base.DatabaseClientError` on failure.
|
|
35
|
+
- Heavy optional dependencies (torch, transformers, gensim, shap, skopt,
|
|
36
|
+
bs4/pdfminer/lxml, matplotlib, jinja2) are imported lazily via
|
|
37
|
+
`qsarkit.base.require("torch")` inside `__init__`/`fit`/the method that
|
|
38
|
+
needs them — never at module top level. This keeps `import qsarkit`
|
|
39
|
+
cheap and lets users install only the extras they need
|
|
40
|
+
(`pip install qsarkit-learn[nlp]`, see `pyproject.toml`).
|
|
41
|
+
|
|
42
|
+
## 3. Mandatory documentation
|
|
43
|
+
|
|
44
|
+
Every algorithm, model, metric, database interface, chemical
|
|
45
|
+
transformation and NLP method **must** have a docstring with a
|
|
46
|
+
`References` section citing the original publication (with DOI when
|
|
47
|
+
available) and, where relevant, the official implementation/API docs it
|
|
48
|
+
wraps (RDKit, scikit-learn, PubChem PUG REST, ChEMBL API, etc.). Follow
|
|
49
|
+
the NumPy docstring style used in `qsarkit/base/transformer.py`. A class
|
|
50
|
+
with a non-trivial algorithm and no `References` section is considered
|
|
51
|
+
incomplete.
|
|
52
|
+
|
|
53
|
+
Example:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
class FooTransformer(MoleculeTransformer):
|
|
57
|
+
"""One-line summary.
|
|
58
|
+
|
|
59
|
+
Longer description of what it does and why.
|
|
60
|
+
|
|
61
|
+
Parameters
|
|
62
|
+
----------
|
|
63
|
+
radius : int
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
References
|
|
67
|
+
----------
|
|
68
|
+
- Author et al. (Year). "Title." Journal, vol(issue), pages.
|
|
69
|
+
https://doi.org/xxxx
|
|
70
|
+
- RDKit documentation: https://www.rdkit.org/docs/...
|
|
71
|
+
"""
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 4. Error handling
|
|
75
|
+
|
|
76
|
+
Only catch/validate at real boundaries (parsing untrusted text, network
|
|
77
|
+
calls, user-supplied SMILES). Do not wrap internal RDKit calls in
|
|
78
|
+
defensive `try/except` "just in case" — trust that a `Chem.Mol` produced
|
|
79
|
+
by an earlier validated step is a valid `Mol`. Raise the specific
|
|
80
|
+
exception from `qsarkit.base.exceptions` that matches the failure, not a
|
|
81
|
+
bare `Exception`.
|
|
82
|
+
|
|
83
|
+
## 5. No premature abstraction
|
|
84
|
+
|
|
85
|
+
Implement exactly the classes/methods named in `PROMPT.md` for your
|
|
86
|
+
module. Do not invent extra plugin systems, registries or config
|
|
87
|
+
frameworks. Where an algorithm genuinely requires a heavy pretrained
|
|
88
|
+
model (ChemBERTa, MPNN, generative models) that cannot be trained/loaded
|
|
89
|
+
in this environment, still implement the full class with a real
|
|
90
|
+
`__init__`/`fit`/`transform`/`forward` contract and real tensor
|
|
91
|
+
plumbing — using `require()` for the heavy dependency — rather than a
|
|
92
|
+
placeholder that just raises `NotImplementedError`. It is fine for such a
|
|
93
|
+
class to *download or expect* a pretrained checkpoint (document the
|
|
94
|
+
expected source in `References`); it must not silently return fake data.
|
|
95
|
+
|
|
96
|
+
## 6. Package layout
|
|
97
|
+
|
|
98
|
+
Each leaf subpackage (e.g. `qsarkit/chemistry/glycans/`) has:
|
|
99
|
+
- one module file per major class (or a couple of closely related classes),
|
|
100
|
+
e.g. `_detector.py`, `_remover.py`, `_descriptors.py`
|
|
101
|
+
- an `__init__.py` that re-exports the public classes
|
|
102
|
+
|
|
103
|
+
Look at `qsarkit/chemistry/standardization/` for a worked example of this
|
|
104
|
+
pattern before writing a new module.
|
|
105
|
+
|
|
106
|
+
## 7. Static typing (mypy / mypyc)
|
|
107
|
+
|
|
108
|
+
The package is checked with `mypy --strict` (see `[tool.mypy]` in
|
|
109
|
+
`pyproject.toml`) and must stay mypyc-compilable. That means:
|
|
110
|
+
|
|
111
|
+
- **Every** function, method and `__init__` has full parameter and return
|
|
112
|
+
annotations. No bare `def f(x):`.
|
|
113
|
+
- `from __future__ import annotations` at the top of every module.
|
|
114
|
+
- Annotate RDKit molecules as `Mol` via a `TYPE_CHECKING` import:
|
|
115
|
+
```python
|
|
116
|
+
from typing import TYPE_CHECKING
|
|
117
|
+
if TYPE_CHECKING:
|
|
118
|
+
from rdkit.Chem import Mol
|
|
119
|
+
```
|
|
120
|
+
and use `"Mol"` / `Iterable["Mol"]` in signatures. Do not annotate them
|
|
121
|
+
as `Any` — RDKit has no stubs, but our own signatures must still be
|
|
122
|
+
precise for readers.
|
|
123
|
+
- Use `npt.NDArray[np.float64]` (`import numpy.typing as npt`) for array
|
|
124
|
+
returns, `list[...]`/`dict[...]` builtins (safe under
|
|
125
|
+
`from __future__ import annotations` on 3.9), `Optional[X]` not `X | None`
|
|
126
|
+
in runtime-evaluated positions, and `Sequence`/`Iterable` for inputs.
|
|
127
|
+
- Prefer `@dataclass` with annotated fields, `Protocol` for duck-typed
|
|
128
|
+
interfaces, and `Literal[...]` for string-enum parameters
|
|
129
|
+
(e.g. `mode: Literal["binary", "count"]`).
|
|
130
|
+
- mypyc-friendliness: avoid monkey-patching instance methods, avoid
|
|
131
|
+
reassigning a name to a different type, and keep class attributes
|
|
132
|
+
declared at class level with annotations.
|
|
133
|
+
|
|
134
|
+
Run `python -m mypy qsarkit/<your_module>` and fix every error in your
|
|
135
|
+
own code before finishing.
|
|
136
|
+
|
|
137
|
+
## 8. Plotting
|
|
138
|
+
|
|
139
|
+
**All plotting uses Plotly, not matplotlib.** `plotly` is a core
|
|
140
|
+
dependency. Plotting functions return a `plotly.graph_objects.Figure`
|
|
141
|
+
(never call `.show()` internally, never write a file unless explicitly
|
|
142
|
+
asked). Import it normally at the top of plotting modules:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
import plotly.graph_objects as go
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Static image export (`fig.write_image`) needs `kaleido`, which is in the
|
|
149
|
+
`reporting` extra — guard that path with `qsarkit.base.require("kaleido")`.
|
|
150
|
+
|
|
151
|
+
## 9. Testing
|
|
152
|
+
|
|
153
|
+
Target **100% statement and branch coverage** of your module
|
|
154
|
+
(`pytest --cov=qsarkit.<module> --cov-branch`). Every public class,
|
|
155
|
+
method, branch and raised exception needs a test. Where a line is only
|
|
156
|
+
reachable with an optional dependency installed, cover it behind
|
|
157
|
+
`pytest.importorskip` rather than excluding it.
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
Add lightweight `pytest` unit tests under `tests/<module_path>/` mirroring
|
|
162
|
+
the package path, using small hardcoded molecules (aspirin, benzene,
|
|
163
|
+
ethanol, a flavonoid glycoside, etc.) — not network calls. Tests for
|
|
164
|
+
network-dependent clients (`qsarkit.databases.*`, live entity linking)
|
|
165
|
+
should mock `requests` rather than hitting real APIs.
|
|
166
|
+
|
|
167
|
+
## Documentation examples must be executable
|
|
168
|
+
|
|
169
|
+
Every example in a docstring or a documentation page is run by the test
|
|
170
|
+
suite (`pytest tests/docs`). Write them as `>>>` doctests, not as
|
|
171
|
+
`.. code-block:: python`, so that an example which stops being true fails
|
|
172
|
+
CI like any other regression.
|
|
173
|
+
|
|
174
|
+
A `code-block` is acceptable only where the snippet genuinely cannot run in
|
|
175
|
+
CI — it needs an optional dependency, a file that does not exist, or a
|
|
176
|
+
third-party package. `tests/docs/test_documentation_examples.py` still
|
|
177
|
+
checks that every `qsarkit` name such a block references actually exists.
|
|
178
|
+
|
|
179
|
+
Numbers in an example must be **measured, not plausible**. Run the code and
|
|
180
|
+
paste what it prints, including when the result is unflattering: a guide
|
|
181
|
+
that quotes an invented R² teaches the reader to expect something the
|
|
182
|
+
package does not deliver.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Frederico Schmitt Kremer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Files to ship in the source distribution beyond what setuptools infers.
|
|
2
|
+
#
|
|
3
|
+
# The test suite is included deliberately: a downstream packager (conda-forge,
|
|
4
|
+
# a Linux distribution) builds from the sdist and needs to be able to run the
|
|
5
|
+
# tests to verify the build. `package.md` and the notebooks are the
|
|
6
|
+
# documentation someone would want alongside the code.
|
|
7
|
+
|
|
8
|
+
include LICENSE
|
|
9
|
+
include README.md
|
|
10
|
+
include package.md
|
|
11
|
+
include CONTRIBUTING_STYLE.md
|
|
12
|
+
include pyproject.toml
|
|
13
|
+
|
|
14
|
+
recursive-include tests *.py
|
|
15
|
+
recursive-include notebooks *.ipynb *.md
|
|
16
|
+
recursive-include typings *.pyi
|
|
17
|
+
|
|
18
|
+
# The RDKit stub shadow is required for `mypy qsarkit` to run at all; see
|
|
19
|
+
# the note in the README.
|
|
20
|
+
include qsarkit/py.typed
|
|
21
|
+
|
|
22
|
+
# Never ship build artefacts or caches.
|
|
23
|
+
global-exclude *.py[cod]
|
|
24
|
+
global-exclude __pycache__
|
|
25
|
+
prune docs
|
|
26
|
+
prune docs-sphinx/build
|
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qsarkit-learn
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: A focused Python library for QSAR modeling: structure curation, molecular representation, model building and selection, OECD-aligned validation, applicability domain, uncertainty and SAR interpretation.
|
|
5
|
+
Author: Frederico Schmitt Kremer
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/omixlab/qsarkit-learn
|
|
8
|
+
Project-URL: Documentation, https://omixlab.github.io/qsarkit-learn/
|
|
9
|
+
Project-URL: Repository, https://github.com/omixlab/qsarkit-learn
|
|
10
|
+
Project-URL: Issues, https://github.com/omixlab/qsarkit-learn/issues
|
|
11
|
+
Project-URL: Changelog, https://omixlab.github.io/qsarkit-learn/changelog.html
|
|
12
|
+
Keywords: qsar,cheminformatics,rdkit,molecular-descriptors,machine-learning,drug-discovery,applicability-domain
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: numpy>=1.23
|
|
29
|
+
Requires-Dist: scipy>=1.9
|
|
30
|
+
Requires-Dist: pandas>=1.5
|
|
31
|
+
Requires-Dist: scikit-learn>=1.2
|
|
32
|
+
Requires-Dist: rdkit>=2022.9.1
|
|
33
|
+
Requires-Dist: networkx>=3.0
|
|
34
|
+
Requires-Dist: plotly>=5.15
|
|
35
|
+
Provides-Extra: embeddings
|
|
36
|
+
Requires-Dist: gensim>=4.3; extra == "embeddings"
|
|
37
|
+
Provides-Extra: nlp
|
|
38
|
+
Requires-Dist: transformers>=4.30; extra == "nlp"
|
|
39
|
+
Requires-Dist: torch>=2.0; extra == "nlp"
|
|
40
|
+
Provides-Extra: explainability
|
|
41
|
+
Requires-Dist: shap>=0.42; extra == "explainability"
|
|
42
|
+
Requires-Dist: lime>=0.2; extra == "explainability"
|
|
43
|
+
Provides-Extra: boosting
|
|
44
|
+
Requires-Dist: xgboost>=1.7; extra == "boosting"
|
|
45
|
+
Requires-Dist: lightgbm>=3.3; extra == "boosting"
|
|
46
|
+
Provides-Extra: reporting
|
|
47
|
+
Requires-Dist: kaleido>=0.2.1; extra == "reporting"
|
|
48
|
+
Requires-Dist: reportlab>=4.0; extra == "reporting"
|
|
49
|
+
Provides-Extra: persistence
|
|
50
|
+
Requires-Dist: skops>=0.9; extra == "persistence"
|
|
51
|
+
Provides-Extra: balancing
|
|
52
|
+
Requires-Dist: imbalanced-learn>=0.11; extra == "balancing"
|
|
53
|
+
Provides-Extra: embedding-viz
|
|
54
|
+
Requires-Dist: umap-learn>=0.5; extra == "embedding-viz"
|
|
55
|
+
Provides-Extra: dev
|
|
56
|
+
Requires-Dist: pytest>=7.3; extra == "dev"
|
|
57
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
58
|
+
Requires-Dist: mypy>=1.5; extra == "dev"
|
|
59
|
+
Requires-Dist: pandas-stubs; extra == "dev"
|
|
60
|
+
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
61
|
+
Requires-Dist: nbformat>=5.9; extra == "dev"
|
|
62
|
+
Requires-Dist: nbclient>=0.8; extra == "dev"
|
|
63
|
+
Requires-Dist: ipykernel>=6.25; extra == "dev"
|
|
64
|
+
Provides-Extra: docs
|
|
65
|
+
Requires-Dist: sphinx>=7.0; extra == "docs"
|
|
66
|
+
Requires-Dist: furo>=2023.9.10; extra == "docs"
|
|
67
|
+
Requires-Dist: sphinx-autodoc-typehints>=1.24; extra == "docs"
|
|
68
|
+
Requires-Dist: sphinx-copybutton>=0.5; extra == "docs"
|
|
69
|
+
Requires-Dist: myst-parser>=2.0; extra == "docs"
|
|
70
|
+
Requires-Dist: sphinx-design>=0.5; extra == "docs"
|
|
71
|
+
Provides-Extra: all
|
|
72
|
+
Requires-Dist: gensim>=4.3; extra == "all"
|
|
73
|
+
Requires-Dist: transformers>=4.30; extra == "all"
|
|
74
|
+
Requires-Dist: torch>=2.0; extra == "all"
|
|
75
|
+
Requires-Dist: shap>=0.42; extra == "all"
|
|
76
|
+
Requires-Dist: lime>=0.2; extra == "all"
|
|
77
|
+
Requires-Dist: xgboost>=1.7; extra == "all"
|
|
78
|
+
Requires-Dist: lightgbm>=3.3; extra == "all"
|
|
79
|
+
Requires-Dist: kaleido>=0.2.1; extra == "all"
|
|
80
|
+
Requires-Dist: reportlab>=4.0; extra == "all"
|
|
81
|
+
Requires-Dist: umap-learn>=0.5; extra == "all"
|
|
82
|
+
Requires-Dist: skops>=0.9; extra == "all"
|
|
83
|
+
Requires-Dist: imbalanced-learn>=0.11; extra == "all"
|
|
84
|
+
Dynamic: license-file
|
|
85
|
+
|
|
86
|
+
# qsarkit-learn
|
|
87
|
+
|
|
88
|
+
A focused, open-source Python library for QSAR (Quantitative Structure-Activity Relationship) modeling.
|
|
89
|
+
|
|
90
|
+
**[Documentation](https://omixlab.github.io/qsarkit-learn/)** | **[Package reference](package.md)** | **[Notebooks](notebooks/)** | **[Source Code](https://github.com/omixlab/qsarkit-learn)**
|
|
91
|
+
|
|
92
|
+
`qsarkit-learn` covers the QSAR workflow proper — curating structures, turning them into features, fitting and validating a model, defining where it applies, and interpreting what it learned. It deliberately stops there: it is not a literature-mining, database-retrieval, docking or de-novo design toolkit, and does not pretend to be.
|
|
93
|
+
|
|
94
|
+
## The Workflow
|
|
95
|
+
|
|
96
|
+
The package is organized around the core stages of a QSAR project:
|
|
97
|
+
|
|
98
|
+
1. **Curation** (`qsarkit.chemistry`, `qsarkit.data_quality`): Curate structures and resolve activity data conflicts.
|
|
99
|
+
2. **Representation** (`qsarkit.representation`): Convert molecules into machine-learning ready features (descriptors, fingerprints, learned embeddings).
|
|
100
|
+
3. **Modeling** (`qsarkit.models`, `qsarkit.model_selection`, `qsarkit.feature_selection`): Select features and fit models.
|
|
101
|
+
4. **Validation** (`qsarkit.validation`, `qsarkit.metrics`): Evaluate models aligned with OECD principles.
|
|
102
|
+
5. **Applicability Domain** (`qsarkit.applicability`): Define the chemical space where your model's predictions are trustworthy.
|
|
103
|
+
6. **Uncertainty** (`qsarkit.uncertainty`): Estimate prediction confidence intervals and error bars.
|
|
104
|
+
7. **Interpretation** (`qsarkit.sar`, `qsarkit.explainability`): Explain model decisions and identify activity cliffs.
|
|
105
|
+
8. **Reporting** (`qsarkit.reporting`, `qsarkit.persistence`): Generate model reports and save models reproducibly.
|
|
106
|
+
|
|
107
|
+
## Design Principles
|
|
108
|
+
|
|
109
|
+
- **Everything is a scikit-learn estimator**: Transformers accept `Iterable[rdkit.Chem.Mol]` and implement `fit` / `transform`. Models implement `fit` / `predict`. Everything composes seamlessly in `sklearn.pipeline.Pipeline`, works with `GridSearchCV`, and supports `clone()`.
|
|
110
|
+
- **Every algorithm cites its source**: Each class documents its original scientific publication with a DOI.
|
|
111
|
+
- **Typed and checked**: The codebase is strictly typed (`mypy --strict`) and ships with a `py.typed` marker.
|
|
112
|
+
- **Every example is executed**: Docstrings, guide pages, API reference and notebooks all run in the test suite, so none of them can go stale silently.
|
|
113
|
+
- **Narrow on purpose**: Everything here earns its place in the core QSAR workflow. Data acquisition and generative modeling are deliberately kept out of scope to maintain a highly trustworthy, specialized tool.
|
|
114
|
+
|
|
115
|
+
## Installation
|
|
116
|
+
|
|
117
|
+
Install from PyPI with pip. Python 3.9+ is required.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pip install qsarkit-learn
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
> The distribution is **`qsarkit-learn`**; the package you import is **`qsarkit`** — the same split as `scikit-learn` and `sklearn`. The bare name `qsarkit` on PyPI belongs to an unrelated project.
|
|
124
|
+
|
|
125
|
+
The package uses optional dependencies to avoid bloating your environment. You can install specific extras depending on your use case:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# For embeddings and NLP-based representations
|
|
129
|
+
pip install qsarkit-learn[embeddings,nlp]
|
|
130
|
+
|
|
131
|
+
# For tree-based models and explainability tools
|
|
132
|
+
pip install qsarkit-learn[boosting,explainability]
|
|
133
|
+
|
|
134
|
+
# For pickle-free model saving and PDF reports
|
|
135
|
+
pip install qsarkit-learn[persistence,reporting]
|
|
136
|
+
|
|
137
|
+
# To install everything
|
|
138
|
+
pip install qsarkit-learn[all]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
A missing optional dependency raises an error naming the extra that provides it, rather than an `ImportError` you have to interpret.
|
|
142
|
+
|
|
143
|
+
| Extra | Enables |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `embeddings` | Mol2Vec embeddings (`gensim`) |
|
|
146
|
+
| `nlp` | ChemBERTa embeddings, MC-dropout (`transformers`, `torch`) |
|
|
147
|
+
| `explainability` | SHAP and LIME attribution |
|
|
148
|
+
| `boosting` | XGBoost and LightGBM estimators |
|
|
149
|
+
| `embedding_viz` | UMAP chemical-space projections (`umap-learn`) |
|
|
150
|
+
| `reporting` | PDF and static image export (`reportlab`, `kaleido`) |
|
|
151
|
+
| `persistence` | Pickle-free model saving (`skops`) |
|
|
152
|
+
| `balancing` | imbalanced-learn samplers |
|
|
153
|
+
|
|
154
|
+
## A First Example
|
|
155
|
+
|
|
156
|
+
Here is a simple example showing how to curate a molecule and analyze the Structure-Activity Relationship (SAR) of a dataset:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from rdkit import Chem
|
|
160
|
+
from qsarkit.chemistry import MolecularStandardizer
|
|
161
|
+
from qsarkit.sar import activity_cliff_report
|
|
162
|
+
|
|
163
|
+
# Curate: strip the salt, neutralize the charge
|
|
164
|
+
standardizer = MolecularStandardizer()
|
|
165
|
+
mol = Chem.MolFromSmiles("CC(=O)Oc1ccccc1C(=O)[O-].[Na+]")
|
|
166
|
+
curated_mols = standardizer.transform([mol])
|
|
167
|
+
print(Chem.MolToSmiles(curated_mols[0]))
|
|
168
|
+
# Output: 'CC(=O)Oc1ccccc1C(=O)O'
|
|
169
|
+
|
|
170
|
+
# Diagnose a dataset before modeling it to find activity cliffs
|
|
171
|
+
# (Assuming `mols` is a list of RDKit molecules and `pIC50_values` is an array of activities)
|
|
172
|
+
report = activity_cliff_report(mols, pIC50_values)
|
|
173
|
+
print(report["cliff_ratio"]) # Proportion of the SAR that is discontinuous
|
|
174
|
+
print(report["top_transformations"]) # R-group swaps that cause the cliffs
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## The Functional Pipe API
|
|
178
|
+
|
|
179
|
+
A second way to write the same workflow, reading in the order the work happens — in the spirit of R's `%>%`. It is not a separate implementation: `featurize` takes any transformer and `fit` any estimator, so the pipe reaches the whole package.
|
|
180
|
+
|
|
181
|
+
`molecules()` accepts RDKit molecules, SMILES and InChI in any mixture, and every step keeps `y` index-aligned with the molecules — dropping a molecule drops its label with it.
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from qsarkit.functional import *
|
|
185
|
+
|
|
186
|
+
train, test = (
|
|
187
|
+
molecules(smiles_or_inchi, activities)
|
|
188
|
+
>> standardize() >> drop_invalid() # MoleculeSet
|
|
189
|
+
>> remove_duplicates(agg="mean")
|
|
190
|
+
>> fingerprint("morgan", n_bits=2048) # -> FeatureSet
|
|
191
|
+
>> split("scaffold", test_size=0.2)
|
|
192
|
+
)
|
|
193
|
+
model = train >> select_features(k=200) >> fit("rf")
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Steps compose, so a curation protocol can be defined once and applied to train and test alike:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
curate = standardize() >> drop_invalid() >> remove_duplicates(max_spread=1.0)
|
|
200
|
+
train = molecules(train_smiles, train_y) >> curate
|
|
201
|
+
test = molecules(test_smiles, test_y) >> curate
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
> **Use `>>`, not `>`.** Python parses `a > b > c` as the chained comparison `(a > b) and (b > c)`, so a `>`-based pipe silently discards everything but the last two stages. Piping with `>` raises a `TypeError` explaining why.
|
|
205
|
+
|
|
206
|
+
A pipeline is a graph, and drawing it is the quickest way to confirm the stages are in the order you meant:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
pipe = standardize() >> drop_invalid() >> fingerprint() >> scale() >> fit("rf")
|
|
210
|
+
|
|
211
|
+
pipe.plot() # a Plotly figure, no extra dependency
|
|
212
|
+
pipe.render("workflow.pdf") # PNG / PDF / SVG
|
|
213
|
+
print(pipe.to_dot()) # Graphviz DOT source
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Any Estimator You Like
|
|
217
|
+
|
|
218
|
+
`QSARRegressor` and `QSARClassifier` dispatch on a name (`"rf"`, `"svm"`, `"gbm"`, `"xgboost"`, `"lightgbm"`, `"knn"`, `"pls"`, `"gp"`, …), but they also accept anything following the scikit-learn `fit`/`predict` protocol — XGBoost, LightGBM, CatBoost, or your own wrapper — as a class or an instance:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from catboost import CatBoostRegressor
|
|
222
|
+
|
|
223
|
+
model = QSARRegressor(CatBoostRegressor,
|
|
224
|
+
model_params={"depth": 6},
|
|
225
|
+
fit_params={"verbose": False}).fit(X, y)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`model_args` / `model_params` go to the constructor; `fit_params`, `predict_params` and `predict_proba_params` reach arguments that belong to the call rather than the constructor. An instance you pass is cloned, never mutated, and the facade remains a real scikit-learn estimator.
|
|
229
|
+
|
|
230
|
+
## Honest Validation
|
|
231
|
+
|
|
232
|
+
The split is the experiment. A random split of a QSAR dataset measures *interpolation*, because public sets are dense with near-duplicate analogues. On the demo dataset that difference is Q²F1 = 0.82 random against −1.0 by scaffold — same data, same model.
|
|
233
|
+
|
|
234
|
+
OECD principle 4 asks for three separate things, and a single R² addresses only the first:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from qsarkit.validation import BootstrapValidator, CrossValidator, YScrambling
|
|
238
|
+
|
|
239
|
+
CrossValidator(n_splits=5).evaluate(model, X_train, y_train) # predictivity
|
|
240
|
+
YScrambling(n_iterations=100).run(model, X_train, y_train)["p_value"] # robustness
|
|
241
|
+
BootstrapValidator(n_iterations=100).run(model, X_train, y_train) # precision
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
y-randomization is the check that catches the classic QSAR failure — a few dozen compounds described by thousands of descriptors, where something will always correlate. It is cheap, so there is no excuse for omitting it.
|
|
245
|
+
|
|
246
|
+
For classification, three traps worth knowing about:
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
from qsarkit.metrics import calibration_report, optimal_threshold, threshold_report
|
|
250
|
+
|
|
251
|
+
# `predict()` cuts at 0.5, which is almost never right on an imbalanced set.
|
|
252
|
+
optimal_threshold(y_val, scores, criterion="mcc")
|
|
253
|
+
optimal_threshold(y_val, scores, criterion="cost", cost_fn=5.0, cost_fp=1.0)
|
|
254
|
+
threshold_report(y_val, scores) # every criterion, against the 0.5 default
|
|
255
|
+
|
|
256
|
+
# ROC-AUC depends only on the *ranking* of scores, so a model can have
|
|
257
|
+
# excellent AUC and useless probabilities. Check before you threshold them.
|
|
258
|
+
calibration_report(y_val, scores)["brier_skill_score"]
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
And for regression, every metric here assumes roughly normal, homoscedastic errors — when that fails, the numbers still compute and quietly mean something else:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
from qsarkit.metrics import qq_data, residual_normality
|
|
265
|
+
|
|
266
|
+
residual_normality(y_test, y_pred) # skew, kurtosis, heteroscedasticity
|
|
267
|
+
qq_data(y_test - y_pred) # the data behind a normal Q-Q plot
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## Applicability Domain
|
|
271
|
+
|
|
272
|
+
A prediction outside the domain is not *wrong* — it is unsupported by the training data, which is a different claim and the one regulators ask about.
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
from qsarkit.applicability import ADAnalyzer, TanimotoSimilarityAD
|
|
276
|
+
|
|
277
|
+
domain = TanimotoSimilarityAD(threshold=0.35).fit(X_train)
|
|
278
|
+
report = ADAnalyzer(domain).fit(X_train).report(X_test, y_test, y_pred)
|
|
279
|
+
report["rmse_ratio"] # > 1 means the domain is doing its job
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
A domain with 100% coverage has told you nothing — and usually indicates a random split rather than a good model.
|
|
283
|
+
|
|
284
|
+
## Explaining a Model on the Molecule
|
|
285
|
+
|
|
286
|
+
SHAP and LIME attribute a prediction to *features*. For a fingerprint model those are hash buckets, and "bit 1743 contributed +0.21" is not an explanation a chemist can act on. `AttributionAtomMapper` projects it back onto atoms through the fingerprint's bit-provenance map, and RDKit draws the result:
|
|
287
|
+
|
|
288
|
+
```python
|
|
289
|
+
from qsarkit.explainability import AttributionAtomMapper, draw_atom_weights
|
|
290
|
+
|
|
291
|
+
mapper = AttributionAtomMapper(fingerprint)
|
|
292
|
+
weights = mapper.from_shap(mol, explainer, X, index=0)
|
|
293
|
+
|
|
294
|
+
mapper.collision_rate(mol) # how much to trust the picture
|
|
295
|
+
svg = draw_atom_weights(mol, weights) # RDKit similarity map
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Saving a Model That Still Works Next Year
|
|
299
|
+
|
|
300
|
+
**Not pickle.** A pickled model embeds the exact class layout of every object, so a file written under one scikit-learn release can fail to load — or load into a subtly different object — under the next; and loading one executes arbitrary code.
|
|
301
|
+
|
|
302
|
+
`qsarkit.persistence` writes a directory bundle instead: plain JSON metadata beside a [skops](https://skops.readthedocs.io) representation of the estimator, which stores parameters as data and refuses to reconstruct untrusted types.
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
from qsarkit.persistence import ModelMetadata, inspect_bundle, load_model, save_model
|
|
306
|
+
|
|
307
|
+
path = save_model(
|
|
308
|
+
model,
|
|
309
|
+
"egfr_pIC50",
|
|
310
|
+
pipeline=fingerprint, # so the bundle can take molecules
|
|
311
|
+
metadata=ModelMetadata(name="EGFR", endpoint="pIC50 (-log10 M)"),
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
inspect_bundle(path)["untrusted"] # safe to run on a stranger's bundle
|
|
315
|
+
bundle = load_model(path)
|
|
316
|
+
bundle.predict_mols(new_mols) # straight from structures
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
A feature-width mismatch is refused rather than producing confident nonsense, and a model loaded under different package versions says so.
|
|
320
|
+
|
|
321
|
+
## Reports
|
|
322
|
+
|
|
323
|
+
`QSARReport` renders to plain text, Markdown, HTML, JSON and PDF, with tables and plots carried into each. `OECDReportBuilder` structures the same material around the five validation principles and tracks which are **unaddressed** — a submission fails review over a principle nobody noticed was missing.
|
|
324
|
+
|
|
325
|
+
```python
|
|
326
|
+
from qsarkit.reporting import OECDReportBuilder, QSARReport
|
|
327
|
+
|
|
328
|
+
builder = OECDReportBuilder(title="QMRF for EGFR model", endpoint="pIC50")
|
|
329
|
+
builder.add_evidence(1, True, {"endpoint": "pIC50, CHEMBL203"})
|
|
330
|
+
builder.unaddressed # [2, 3, 4, 5] — explicit gaps
|
|
331
|
+
|
|
332
|
+
builder.build().to_pdf("qmrf.pdf")
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
All plotting returns `plotly.graph_objects.Figure` objects. Functions never call `.show()` and never write files, so the same figure composes into a notebook, a dashboard and a report.
|
|
336
|
+
|
|
337
|
+
## Learning the Package
|
|
338
|
+
|
|
339
|
+
- **[`package.md`](package.md)** — the whole package organization in one file: every subpackage, its main classes and functions, and the literature behind them.
|
|
340
|
+
- **[Notebooks](notebooks/)** — five worked walkthroughs covering every public subpackage, committed with their output:
|
|
341
|
+
1. [Curation and the functional API](notebooks/01_curation_and_the_functional_api.ipynb)
|
|
342
|
+
2. [Representation and chemical space](notebooks/02_representation_and_chemical_space.ipynb)
|
|
343
|
+
3. [Modelling, validation and applicability](notebooks/03_modeling_validation_and_applicability.ipynb)
|
|
344
|
+
4. [SAR, explainability and reporting](notebooks/04_sar_explainability_and_reporting.ipynb)
|
|
345
|
+
5. [Classification, calibration and deployment](notebooks/05_classification_calibration_and_deployment.ipynb)
|
|
346
|
+
- **[Documentation](https://omixlab.github.io/qsarkit-learn/)** — the API reference, with a worked example and scientific references for every class.
|
|
347
|
+
|
|
348
|
+
The notebooks use a deliberately *hard* 24-compound dataset: one planted activity-cliff outlier, distinct scaffold families, non-normal residuals. Several of them show models scoring badly on it. That is the point — a worked example where everything succeeds teaches nothing about the failure modes these tools exist to detect.
|
|
349
|
+
|
|
350
|
+
## Development
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
git clone https://github.com/omixlab/qsarkit-learn
|
|
354
|
+
cd qsarkit-learn
|
|
355
|
+
pip install -e ".[dev]"
|
|
356
|
+
|
|
357
|
+
pytest # the suite
|
|
358
|
+
pytest -m slow # plus executing the notebooks
|
|
359
|
+
pytest --cov=qsarkit --cov-branch # with coverage
|
|
360
|
+
mypy qsarkit # strict type check
|
|
361
|
+
ruff check qsarkit # lint
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Building the documentation. `docs-sphinx/` is the source; `docs/` is the
|
|
365
|
+
built site served by GitHub Pages, and it is committed, so rebuilding it is
|
|
366
|
+
part of preparing a commit that touches the docs:
|
|
367
|
+
|
|
368
|
+
```bash
|
|
369
|
+
pip install -e ".[docs]"
|
|
370
|
+
|
|
371
|
+
make docs # rebuild and copy the site into docs/ — the one to run
|
|
372
|
+
make preview # build into docs-sphinx/build/html, leaving docs/ alone
|
|
373
|
+
make doctest # execute every example in the documentation
|
|
374
|
+
make linkcheck # verify external links resolve
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
`make docs` builds from scratch with warnings treated as errors, so a broken
|
|
378
|
+
cross-reference or a page deleted from the source cannot reach the published
|
|
379
|
+
site. The same targets exist inside `docs-sphinx/` if you prefer to work
|
|
380
|
+
there (`make publish` is the one that writes to `../docs`).
|
|
381
|
+
|
|
382
|
+
> `typings/` holds a local stub shadow for RDKit. The `rdkit-stubs` bundled with the RDKit wheel contain an auto-generation bug — a C++ enum member named `None`, which is an illegal annotation target — that otherwise aborts every mypy run and silently hides all real type errors. Don't delete it.
|
|
383
|
+
|
|
384
|
+
## OECD Compliance
|
|
385
|
+
|
|
386
|
+
The package is organized around the five OECD validation principles:
|
|
387
|
+
|
|
388
|
+
| # | Principle | Where it lives |
|
|
389
|
+
|---|---|---|
|
|
390
|
+
| 1 | A defined endpoint | `ModelMetadata`, `QSARReport` |
|
|
391
|
+
| 2 | An unambiguous algorithm | Documented hyperparameters; `MoleculeSet.history`; `persistence` provenance |
|
|
392
|
+
| 3 | A defined applicability domain | `qsarkit.applicability` |
|
|
393
|
+
| 4 | Goodness-of-fit, robustness, predictivity | `qsarkit.metrics`, `qsarkit.validation` |
|
|
394
|
+
| 5 | A mechanistic interpretation, if possible | `qsarkit.sar`, `qsarkit.explainability` |
|
|
395
|
+
|
|
396
|
+
> OECD (2007). *Guidance Document on the Validation of (Quantitative) Structure-Activity Relationship [(Q)SAR] Models.* OECD Series on Testing and Assessment No. 69, ENV/JM/MONO(2007)2. [doi:10.1787/9789264085442-en](https://doi.org/10.1787/9789264085442-en)
|
|
397
|
+
|
|
398
|
+
## Contributing
|
|
399
|
+
|
|
400
|
+
We welcome contributions! Please see the [`CONTRIBUTING_STYLE.md`](CONTRIBUTING_STYLE.md) guide for our conventions on class structures, documentation, error handling, static typing (`mypy`), and unit testing.
|
|
401
|
+
|
|
402
|
+
## Citing
|
|
403
|
+
|
|
404
|
+
If `qsarkit-learn` contributes to work you publish, please cite the package along with the primary reference for whichever algorithm you used — each class docstring names it.
|
|
405
|
+
|
|
406
|
+
## License
|
|
407
|
+
|
|
408
|
+
MIT — see [`LICENSE`](LICENSE).
|