Base¶
Shared estimator base classes, the exception hierarchy, and the
lazy-import helper that keeps heavy optional dependencies out of
import qsarkit.
Writing a transformer¶
The contract is one method. Implement _transform on a list of
molecules, and inherit input validation, fit, fit_transform and
full scikit-learn compatibility:
>>> import numpy as np
>>> from rdkit import Chem
>>> from qsarkit.base import MoleculeTransformer
>>> class HeavyAtomCount(MoleculeTransformer):
... def _transform(self, mols):
... return np.array([[m.GetNumHeavyAtoms()] for m in mols], dtype=float)
>>> HeavyAtomCount().fit_transform(demo_mols).shape
(24, 1)
It is a real estimator, so it composes and clones:
>>> from sklearn.base import clone
>>> clone(HeavyAtomCount())
HeavyAtomCount()
Three bases, by what they return¶
MoleculeTransformerMolecules in, feature matrix out.
MoleculeToMoleculeTransformerMolecules in, molecules out — standardization and curation. The separate type is what lets these chain with each other.
FittableMoleculeTransformerAdds an
is_fittedflag and a guard, so callingtransformbeforefitraises rather than producing meaningless features:
>>> from qsarkit.base import FittableMoleculeTransformer
>>> class MeanCentredSize(FittableMoleculeTransformer):
... def fit(self, mols, y=None):
... self.mean_ = np.mean([m.GetNumHeavyAtoms() for m in mols])
... self._is_fitted = True
... return self
... def _transform(self, mols):
... self._check_is_fitted()
... return np.array([m.GetNumHeavyAtoms() - self.mean_ for m in mols])
>>> MeanCentredSize().transform(demo_mols)
Traceback (most recent call last):
...
qsarkit.base.exceptions.ModelNotFittedError: MeanCentredSize must be fitted...
Input validation¶
>>> from qsarkit.base import ensure_mol_list
>>> ensure_mol_list([None])
[None]
None passes through deliberately: a molecule that failed an earlier
parsing step must keep its position, because dropping it here would
silently shift every downstream label by one. Anything that is neither a
molecule nor None is a programming error and is reported as one:
>>> ensure_mol_list(["CCO"])
Traceback (most recent call last):
...
qsarkit.base.exceptions.InvalidMoleculeError: Element 0 is not an rdkit.Chem.Mol...
Optional dependencies¶
>>> from qsarkit.base import require
>>> require("numpy").__name__
'numpy'
A missing dependency raises an error naming the extra that provides it,
rather than an ImportError the user has to interpret:
>>> require("nonexistent_package_xyz")
Traceback (most recent call last):
...
qsarkit.base.exceptions.OptionalDependencyError: This feature requires...
API¶
Shared base classes, exceptions and helpers used across all of qsarkit.
- exception qsarkit.base.QsarkitError[source]¶
Bases:
ExceptionBase class for all qsarkit exceptions.
- exception qsarkit.base.InvalidMoleculeError[source]¶
Bases:
QsarkitErrorRaised when an input cannot be parsed or sanitized into an RDKit Mol.
- exception qsarkit.base.ModelNotFittedError[source]¶
Bases:
QsarkitErrorRaised when
.transform/.predictis called before.fit.
- exception qsarkit.base.OptionalDependencyError(package, extra=None)[source]¶
Bases:
QsarkitErrorRaised when an optional dependency required by a feature is missing.
- qsarkit.base.require(module_name)[source]¶
Import and return
module_name, raising a helpful error if absent.- Parameters:
module_name (
str) – Fully qualified module name, e.g."torch"or"pdfminer.high_level".- Returns:
The imported module object.
- Return type:
- Raises:
OptionalDependencyError – If the module is not installed. The error message names the pip extra (
qsarkit-learn[extra]) that installs it.
Examples
>>> from qsarkit.base import require >>> require("numpy").__name__ 'numpy'
A missing dependency raises an error naming the extra that provides it, rather than an
ImportErrorthe caller has to interpret:>>> require("nonexistent_package_xyz") Traceback (most recent call last): ... qsarkit.base.exceptions.OptionalDependencyError: This feature requires...
Call it inside
__init__orfit, never at module import time – that is what keepsimport qsarkitfrom pulling in PyTorch.
- class qsarkit.base.MoleculeTransformer[source]¶
Bases:
BaseEstimator,TransformerMixin,ABCAbstract base for stateless/stateful molecule -> X transformers.
Subclasses implement
_transform()and operate onIterable[rdkit.Chem.Mol].fitis a no-op by default (most chemistry transformers are stateless), but subclasses that need to learn parameters from data (e.g. a fingerprint vocabulary or a Mol2Vec embedding model) should override it.Examples
See the module docstring for a complete subclass. Input validation is inherited, so a subclass never has to check its own arguments:
>>> import numpy as np >>> from qsarkit.base import MoleculeTransformer >>> class RingCount(MoleculeTransformer): ... def _transform(self, mols): ... return np.array([m.GetRingInfo().NumRings() for m in mols]) >>> RingCount().transform(["not a molecule"]) Traceback (most recent call last): ... qsarkit.base.exceptions.InvalidMoleculeError: Element 0 is not an rdkit.Chem.Mol...
- set_fit_request(*, mols='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
- set_transform_request(*, mols='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
transformmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed totransformif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it totransform.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
- class qsarkit.base.MoleculeToMoleculeTransformer[source]¶
Bases:
MoleculeTransformer,ABCBase for transformers that map Mol -> Mol (standardization, curation, …).
Identical to
MoleculeTransformerin behaviour; the separate type documents thattransformreturns molecules rather than a feature matrix, so these can be chained with each other.Examples
>>> from rdkit import Chem >>> from qsarkit.base import MoleculeToMoleculeTransformer >>> class StripStereo(MoleculeToMoleculeTransformer): ... def _transform(self, mols): ... out = [] ... for m in mols: ... copy = Chem.Mol(m) ... Chem.RemoveStereochemistry(copy) ... out.append(copy) ... return out >>> mol = Chem.MolFromSmiles("C[C@H](N)C(=O)O") >>> Chem.MolToSmiles(StripStereo().transform([mol])[0]) 'CC(N)C(=O)O'
- set_fit_request(*, mols='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
- set_transform_request(*, mols='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
transformmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed totransformif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it totransform.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
- class qsarkit.base.FittableMoleculeTransformer[source]¶
Bases:
MoleculeTransformer,ABCBase for transformers with learned state (embeddings, vocabularies).
Adds an
is_fittedflag and a_check_is_fittedguard, so callingtransformbeforefitraises a clear error instead of producing silently meaningless features.Examples
>>> import numpy as np >>> from rdkit import Chem >>> from qsarkit.base import FittableMoleculeTransformer >>> class MeanCentredSize(FittableMoleculeTransformer): ... def fit(self, mols, y=None): ... self.mean_ = np.mean([m.GetNumHeavyAtoms() for m in mols]) ... self._is_fitted = True ... return self ... def _transform(self, mols): ... self._check_is_fitted() ... return np.array([m.GetNumHeavyAtoms() - self.mean_ for m in mols]) >>> mols = [Chem.MolFromSmiles(s) for s in ("CCO", "c1ccccc1")] >>> transformer = MeanCentredSize() >>> transformer.is_fitted False >>> transformer.transform(mols) Traceback (most recent call last): ... qsarkit.base.exceptions.ModelNotFittedError: MeanCentredSize must be fitted... >>> transformer.fit(mols).transform(mols).tolist() [-1.5, 1.5]
- set_fit_request(*, mols='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
fitmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed tofitif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it tofit.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
- set_transform_request(*, mols='$UNCHANGED$')¶
Configure whether metadata should be requested to be passed to the
transformmethod.Note that this method is only relevant when this estimator is used as a sub-estimator within a meta-estimator and metadata routing is enabled with
enable_metadata_routing=True(seesklearn.set_config()). Please check the User Guide on how the routing mechanism works.The options for each parameter are:
True: metadata is requested, and passed totransformif provided. The request is ignored if metadata is not provided.False: metadata is not requested and the meta-estimator will not pass it totransform.None: metadata is not requested, and the meta-estimator will raise an error if the user provides it.str: metadata should be passed to the meta-estimator with this given alias instead of the original name.
The default (
sklearn.utils.metadata_routing.UNCHANGED) retains the existing request. This allows you to change the request for some parameters and not others.Added in version 1.3.
- qsarkit.base.ensure_mol_list(mols)[source]¶
Materialize an
Iterable[Mol]into a list, validating entries.- Parameters:
mols (
Iterable[Any]) – Iterable ofrdkit.Chem.Molobjects.Noneentries are allowed through (representing molecules that failed an earlier parsing step) and are left untouched so callers can decide how to handle them positionally.- Returns:
The materialized list.
- Return type:
- Raises:
InvalidMoleculeError – If an entry is neither
Nonenor an RDKitMol.
Examples
>>> from rdkit import Chem >>> from qsarkit.base import ensure_mol_list >>> len(ensure_mol_list(Chem.MolFromSmiles(s) for s in ("CCO", "CCN"))) 2
Noneis allowed through, because a molecule that failed an earlier parsing step must keep its position – dropping it here would silently shift every downstream label by one:>>> ensure_mol_list([None]) [None]
Anything else is a programming error and is reported as one:
>>> ensure_mol_list(["CCO"]) Traceback (most recent call last): ... qsarkit.base.exceptions.InvalidMoleculeError: Element 0 is not an rdkit.Chem.Mol...
References¶
Pedregosa, F. et al. (2011). “Scikit-learn: Machine Learning in Python.” J. Mach. Learn. Res., 12, 2825-2830. https://jmlr.org/papers/v12/pedregosa11a.html
Buitinck, L. et al. (2013). “API Design for Machine Learning Software: Experiences from the scikit-learn Project.” ECML PKDD Workshop. arXiv:1309.0238