Utilities

I/O, unit conversion, validation helpers, logging and package constants.

Activity units

The single most damaging silent error in QSAR is a mixed activity column. Converting to a p-scale first makes the units explicit and puts the values on the scale the models assume.

>>> from qsarkit.utils import from_pactivity, to_pactivity
>>> float(to_pactivity(1.0, unit="nM"))
9.0
>>> float(to_pactivity(1000.0, unit="nM"))
6.0
>>> round(float(from_pactivity(9.0, "nM")), 6)
1.0

pIC50 = −log10(IC50 in molar), so 1 nM is 9 and a thousand-fold weaker compound is 6. Working on the p-scale also makes the errors approximately normal, which is what every regression metric here assumes.

>>> from qsarkit.utils import convert_concentration, nm_to_molar
>>> round(float(nm_to_molar(1000.0)), 12)
1e-06
>>> round(float(convert_concentration(1.0, "uM", "nM")), 6)
1000.0

Binding free energy, for comparison with calorimetry or docking scores:

>>> from qsarkit.utils import pactivity_to_delta_g
>>> round(float(pactivity_to_delta_g(9.0)), 2)
-12.28

I/O

>>> from qsarkit.utils import mols_to_dataframe, read_smiles, write_smiles
>>> frame = mols_to_dataframe(demo_mols[:3], extra={"pIC50": DEMO_Y[:3]})
>>> list(frame.columns)
['smiles', 'pIC50']
>>> import tempfile, os
>>> path = os.path.join(tempfile.mkdtemp(), "demo.smi")
>>> write_smiles(demo_mols[:3], path)      # returns the count written
3
>>> len(read_smiles(path))
3

Validation helpers

>>> from qsarkit.utils import check_X_y_mols, check_mols
>>> len(check_mols(demo_mols))
24
>>> mols, y = check_X_y_mols(demo_mols, DEMO_Y)
>>> len(mols) == len(y)
True

Constants

>>> from qsarkit.utils import CONCENTRATION_TO_MOLAR, LIPINSKI_THRESHOLDS
>>> CONCENTRATION_TO_MOLAR["nM"]
1e-09
>>> LIPINSKI_THRESHOLDS["mw_max"]
500.0

API

Shared helpers: I/O, logging, validation and unit conversion.

Examples

>>> from qsarkit.utils import to_pactivity
>>> float(to_pactivity(1.0, unit="nM"))
9.0
qsarkit.utils.read_smiles(source, delimiter=None, smiles_column=0, name_column=1, has_header=False, sanitize=True, on_error='skip')[source]

Read SMILES from a file path or an iterable of strings into RDKit Mols.

Parameters:
  • source (Union[str, PathLike[str], Iterable[str]]) – Either a path to a .smi/.txt file, or an already-materialized iterable of SMILES strings (or whitespace/delimiter separated lines).

  • delimiter (Optional[str]) – Field delimiter for multi-column lines. None splits on arbitrary whitespace (the classic .smi convention).

  • smiles_column (int) – Index of the SMILES field within each split line.

  • name_column (Optional[int]) – Index of an optional molecule-name field; stored on the molecule as the _Name property when present. Pass None to ignore names.

  • has_header (bool) – Skip the first line when reading from a file/iterable.

  • sanitize (bool) – Run RDKit sanitization on parsing.

  • on_error (str) – What to do with unparsable records: drop them, insert None in their position (preserving alignment with the source), or raise InvalidMoleculeError.

Returns:

The parsed molecules.

Return type:

List[Any]

Raises:

Examples

>>> from qsarkit.utils import read_smiles
>>> mols = read_smiles(["CCO ethanol", "c1ccccc1 benzene"])
>>> [m.GetProp("_Name") for m in mols]
['ethanol', 'benzene']

References

qsarkit.utils.write_smiles(mols, path, names=None, isomeric=True, delimiter=' ')[source]

Write molecules to a .smi file.

Parameters:
  • mols (Iterable[Any]) – Molecules to serialize. None entries are skipped.

  • path (Union[str, PathLike[str]]) – Destination file.

  • names (Optional[Sequence[str]]) – Per-molecule names. Defaults to the molecule’s _Name property when set, else no name column is emitted.

  • isomeric (bool) – Emit isomeric SMILES (retain stereochemistry).

  • delimiter (str) – Field separator.

Returns:

Number of molecules written.

Return type:

int

Examples

>>> import tempfile, os
>>> from rdkit import Chem
>>> from qsarkit.utils import write_smiles, read_smiles
>>> p = os.path.join(tempfile.mkdtemp(), "m.smi")
>>> write_smiles([Chem.MolFromSmiles("CCO")], p)
1
>>> len(read_smiles(p))
1

References

qsarkit.utils.read_sdf(path, sanitize=True, remove_hs=True, on_error='skip')[source]

Read an MDL SD file into a list of RDKit molecules.

SD-file data fields are preserved as RDKit molecule properties, so they can be recovered with mols_to_dataframe().

Parameters:
  • path (Union[str, PathLike[str]]) – Path to the .sdf / .sd file.

  • sanitize (bool) – Run RDKit sanitization on each record.

  • remove_hs (bool) – Remove explicit hydrogens (RDKit’s implicit-H convention).

  • on_error (str) – Handling of records RDKit fails to parse.

Returns:

Parsed molecules.

Return type:

List[Any]

Raises:

Examples

>>> import tempfile, os
>>> from rdkit import Chem
>>> from qsarkit.utils import write_sdf, read_sdf
>>> p = os.path.join(tempfile.mkdtemp(), "m.sdf")
>>> _ = write_sdf([Chem.MolFromSmiles("CCO")], p)
>>> len(read_sdf(p))
1

References

qsarkit.utils.write_sdf(mols, path, properties=None, kekulize=True)[source]

Write molecules to an MDL SD file, optionally attaching data fields.

Parameters:
  • mols (Iterable[Any]) – Molecules to write. None entries are skipped.

  • path (Union[str, PathLike[str]]) – Destination file.

  • properties (Optional[Dict[str, Sequence[Any]]]) – Extra SD data fields, one sequence per field aligned with mols (e.g. {"pIC50": [7.2, 6.4]}). Values are written with str().

  • kekulize (bool) – Kekulize aromatic rings before writing (standard for MDL formats).

Returns:

Number of records written.

Return type:

int

Raises:

ValueError – If a property sequence is shorter than the molecule list.

Examples

>>> import tempfile, os
>>> from rdkit import Chem
>>> from qsarkit.utils import write_sdf
>>> p = os.path.join(tempfile.mkdtemp(), "m.sdf")
>>> write_sdf([Chem.MolFromSmiles("CCO")], p, {"pIC50": [7.1]})
1

References

  • Dalby et al. (1992). “Description of several chemical structure file formats used by computer programs developed at Molecular Design Limited.” J. Chem. Inf. Comput. Sci., 32(3), 244-255. https://doi.org/10.1021/ci00007a012

qsarkit.utils.mols_to_dataframe(mols, include_smiles=True, include_properties=True, smiles_column='smiles', extra=None)[source]

Flatten molecules and their RDKit properties into a pandas.DataFrame.

Parameters:
  • mols (Iterable[Any]) – Molecules. None entries produce a row of missing values.

  • include_smiles (bool) – Add a canonical isomeric SMILES column.

  • include_properties (bool) – Add one column per RDKit molecule property found across the input (the union of all property names; missing values become None).

  • smiles_column (str) – Name of the SMILES column.

  • extra (Optional[Dict[str, Sequence[Any]]]) – Additional aligned columns, e.g. measured activities.

Returns:

One row per input molecule, in input order.

Return type:

Any

Raises:

ValueError – If an extra sequence length does not match the molecule count.

Examples

>>> from rdkit import Chem
>>> from qsarkit.utils import mols_to_dataframe
>>> df = mols_to_dataframe([Chem.MolFromSmiles("CCO")], extra={"y": [1.0]})
>>> list(df.columns)
['smiles', 'y']

References

qsarkit.utils.dataframe_to_mols(df, smiles_column='smiles', name_column=None, property_columns=None, sanitize=True, on_error='none')[source]

Build RDKit molecules from a SMILES column of a pandas.DataFrame.

Parameters:
  • df (Any) – Source table.

  • smiles_column (str) – Column holding the SMILES strings.

  • name_column (Optional[str]) – Column copied onto each molecule as its _Name property.

  • property_columns (Optional[Sequence[str]]) – Columns copied onto each molecule as RDKit properties (stringified).

  • sanitize (bool) – Run RDKit sanitization on parsing.

  • on_error (str) – Handling of unparsable SMILES. "none" (the default here) keeps positional alignment with the DataFrame rows.

Returns:

The parsed molecules.

Return type:

List[Any]

Raises:

Examples

>>> import pandas as pd
>>> from qsarkit.utils import dataframe_to_mols
>>> df = pd.DataFrame({"smiles": ["CCO", "c1ccccc1"]})
>>> len(dataframe_to_mols(df))
2

References

qsarkit.utils.read_csv_mols(path, smiles_column='smiles', activity_column=None, sanitize=True, on_error='none', **read_csv_kwargs)[source]

Read a CSV of structures, returning (mols, y, dataframe).

Parameters:
  • path (Union[str, PathLike[str]]) – CSV file path.

  • smiles_column (str) – Column holding SMILES strings.

  • activity_column (Optional[str]) – Column holding the target values. When None the returned y is None.

  • sanitize (bool) – Run RDKit sanitization on parsing.

  • on_error (str) – Handling of unparsable SMILES. "none" keeps alignment between mols, y and the DataFrame rows.

  • **read_csv_kwargs (Any) – Forwarded to pandas.read_csv().

Return type:

Any

Returns:

Examples

>>> import tempfile, os, pandas as pd
>>> from qsarkit.utils import read_csv_mols
>>> p = os.path.join(tempfile.mkdtemp(), "d.csv")
>>> pd.DataFrame({"smiles": ["CCO"], "y": [1.0]}).to_csv(p, index=False)
>>> mols, y, df = read_csv_mols(p, activity_column="y")
>>> len(mols), float(y[0])
(1, 1.0)

References

qsarkit.utils.get_logger(name=None)[source]

Return a logger inside the qsarkit namespace.

Parameters:

name (Optional[str]) – Logger name. A bare name ("admet") or a dotted module name ("qsarkit.admet._filters") are both accepted; the result is always rooted at qsarkit. None returns the package root logger.

Returns:

The requested logger. It has no handler of its own; records propagate to the qsarkit root logger, which carries a NullHandler unless configure_logging() was called.

Return type:

Logger

Examples

>>> from qsarkit.utils import get_logger
>>> log = get_logger(__name__)
>>> log.name.startswith("qsarkit")
True

References

qsarkit.utils.configure_logging(level=20, stream=None, fmt='%(asctime)s %(levelname)-8s %(name)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')[source]

Attach a stream handler to the qsarkit root logger.

This is an application-level convenience; libraries importing qsarkit should not call it. Calling it repeatedly replaces the previously installed qsarkit handler rather than stacking duplicates.

Parameters:
  • level (Union[int, str]) – Level for the qsarkit logger, e.g. "DEBUG" or logging.WARNING.

  • stream (Optional[object]) – Destination stream. Defaults to sys.stderr.

  • fmt (str) – logging format string.

  • datefmt (str) – time.strftime format for %(asctime)s.

Returns:

The configured qsarkit root logger.

Return type:

Logger

Examples

>>> import io
>>> from qsarkit.utils import configure_logging, get_logger
>>> buf = io.StringIO()
>>> _ = configure_logging("DEBUG", stream=buf)
>>> get_logger("demo").debug("hello")
>>> "hello" in buf.getvalue()
True

References

qsarkit.utils.check_mols(mols, allow_none=False, min_size=1)[source]

Validate and materialize an Iterable[Mol].

Parameters:
  • mols (Iterable[Any]) – Input molecules.

  • allow_none (bool) – If False a None entry (a molecule that failed an earlier parsing/curation step) raises InvalidMoleculeError. If True None entries are preserved so downstream code can keep positional alignment.

  • min_size (int) – Minimum acceptable number of molecules.

Returns:

The materialized list.

Return type:

List[Any]

Raises:

InvalidMoleculeError – If an element is not an RDKit Mol (or is None while allow_none is False), or if fewer than min_size molecules were supplied.

Examples

>>> from rdkit import Chem
>>> from qsarkit.utils import check_mols
>>> len(check_mols([Chem.MolFromSmiles("CCO")]))
1

References

  • Pedregosa et al. (2011). “Scikit-learn: Machine Learning in Python.” JMLR, 12, 2825-2830.

qsarkit.utils.check_X_y_mols(mols, y=None, allow_none=False, min_size=1, dtype=<class 'float'>)[source]

Validate a (mols, y) pair for a supervised QSAR estimator.

Parameters:
  • mols (Iterable[Any]) – Input molecules.

  • y (Optional[Sequence[Any]]) – Target values. If None only the molecules are validated and the returned target is None (mirrors fit(X, y=None)).

  • allow_none (bool) – Forwarded to check_mols().

  • min_size (int) – Minimum acceptable number of molecules.

  • dtype (Any) – NumPy dtype the target is cast to. Pass None to keep the original dtype (useful for string class labels).

Return type:

Tuple[List[Any], Optional[ndarray]]

Returns:

  • mols (list of rdkit.Chem.Mol) – The validated molecules.

  • y (numpy.ndarray or None) – The target as a 1-D array, or None.

Raises:

Examples

>>> from rdkit import Chem
>>> from qsarkit.utils import check_X_y_mols
>>> mols, y = check_X_y_mols([Chem.MolFromSmiles("CCO")], [1.0])
>>> float(y[0])
1.0

References

  • Pedregosa et al. (2011). “Scikit-learn: Machine Learning in Python.” JMLR, 12, 2825-2830.

qsarkit.utils.nm_to_molar(value)[source]

Convert nanomolar concentrations to molar.

Parameters:

value (Any) – Concentration(s) in nM.

Returns:

The concentration(s) in mol/L.

Return type:

ndarray

Examples

>>> from qsarkit.utils import nm_to_molar
>>> round(float(nm_to_molar(1000.0)), 12)
1e-06

References

qsarkit.utils.convert_concentration(value, from_unit, to_unit='M')[source]

Convert a concentration between the units in CONCENTRATION_TO_MOLAR.

Parameters:
  • value (Any) – Concentration value(s).

  • from_unit (str) – Source and destination units. Supported keys: M, mM, uM, nM, pM, fM (plus the aliases in qsarkit.utils.constants.CONCENTRATION_TO_MOLAR).

  • to_unit (str) – Source and destination units. Supported keys: M, mM, uM, nM, pM, fM (plus the aliases in qsarkit.utils.constants.CONCENTRATION_TO_MOLAR).

Returns:

The converted concentration(s).

Return type:

ndarray

Raises:

ValueError – If either unit is unknown.

Examples

>>> from qsarkit.utils import convert_concentration
>>> round(float(convert_concentration(1.0, "uM", "nM")), 6)
1000.0

References

qsarkit.utils.to_pactivity(value, unit='nM')[source]

Convert a concentration-based activity to its p-scale value.

The p-scale (pIC50, pKi, pEC50, …) is the negative decadic logarithm of the molar concentration:

pX = -log10(C / 1 M)

Working on the p-scale is standard practice in QSAR because it linearises the relationship with free energy of binding and makes the error structure approximately homoscedastic.

Parameters:
  • value (Any) – Activity concentration(s), strictly positive.

  • unit (str) – Unit of value; any key of qsarkit.utils.constants.CONCENTRATION_TO_MOLAR.

Returns:

The p-scale activity value(s). Non-positive or non-finite inputs map to nan.

Return type:

ndarray

Examples

>>> from qsarkit.utils import to_pactivity
>>> float(to_pactivity(1.0, "nM"))
9.0
>>> float(to_pactivity(1000.0, "nM"))
6.0

References

qsarkit.utils.from_pactivity(pvalue, unit='nM')[source]

Invert to_pactivity(), returning a concentration.

Parameters:
  • pvalue (Any) – p-scale activity value(s), e.g. pIC50.

  • unit (str) – Unit of the returned concentration.

Returns:

Concentration(s) expressed in unit.

Return type:

ndarray

Examples

>>> from qsarkit.utils import from_pactivity
>>> round(float(from_pactivity(9.0, "nM")), 6)
1.0

References

qsarkit.utils.pactivity_to_delta_g(pvalue, temperature=298.15, units='kcal')[source]

Convert a p-scale affinity to a binding free energy.

Uses dG = -RT ln(K) = -2.303 R T * pX with K the association constant, valid when the reported p-value is a pKd/pKi (an equilibrium dissociation constant). Applying it to a pIC50 is an approximation that ignores the Cheng-Prusoff correction.

Parameters:
  • pvalue (Any) – p-scale affinity (pKd or pKi).

  • temperature (float) – Absolute temperature in kelvin.

  • units (str) – Energy units of the result (per mole).

Returns:

Binding free energy, negative for favourable binding.

Return type:

ndarray

Examples

>>> from qsarkit.utils import pactivity_to_delta_g
>>> round(float(pactivity_to_delta_g(9.0)), 2)
-12.28

References

  • Cheng & Prusoff (1973). “Relationship between the inhibition constant (Ki) and the concentration of inhibitor which causes 50 per cent inhibition (I50) of an enzymatic reaction.” Biochem. Pharmacol., 22(23), 3099-3108. https://doi.org/10.1016/0006-2952(73)90196-2

References

  • Kalliokoski, T. et al. (2013). “Comparability of Mixed IC50 Data — A Statistical Analysis.” PLoS ONE, 8(4), e61007. doi:10.1371/journal.pone.0061007

  • Bento, A. P. et al. (2014). “The ChEMBL Bioactivity Database: An Update.” Nucleic Acids Res., 42, D1083-D1090. doi:10.1093/nar/gkt1031

  • Tiesinga, E. et al. (2021). “CODATA Recommended Values of the Fundamental Physical Constants: 2018.” Rev. Mod. Phys., 93, 025010. doi:10.1103/RevModPhys.93.025010