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/.txtfile, or an already-materialized iterable of SMILES strings (or whitespace/delimiter separated lines).delimiter (
Optional[str]) – Field delimiter for multi-column lines.Nonesplits on arbitrary whitespace (the classic.smiconvention).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_Nameproperty when present. PassNoneto 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, insertNonein their position (preserving alignment with the source), or raiseInvalidMoleculeError.
- Returns:
The parsed molecules.
- Return type:
- Raises:
InvalidMoleculeError – If
on_error="raise"and a record cannot be parsed.ValueError – If
on_erroris not one of the accepted values.
Examples
>>> from qsarkit.utils import read_smiles >>> mols = read_smiles(["CCO ethanol", "c1ccccc1 benzene"]) >>> [m.GetProp("_Name") for m in mols] ['ethanol', 'benzene']
References
Weininger (1988). “SMILES, a chemical language and information system. 1.” J. Chem. Inf. Comput. Sci., 28(1), 31-36. https://doi.org/10.1021/ci00057a005
RDKit documentation: https://www.rdkit.org/docs/GettingStartedInPython.html
- qsarkit.utils.write_smiles(mols, path, names=None, isomeric=True, delimiter=' ')[source]¶
Write molecules to a
.smifile.- Parameters:
mols (
Iterable[Any]) – Molecules to serialize.Noneentries are skipped.names (
Optional[Sequence[str]]) – Per-molecule names. Defaults to the molecule’s_Nameproperty 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:
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
Weininger (1988). “SMILES, a chemical language and information system. 1.” J. Chem. Inf. Comput. Sci., 28(1), 31-36. https://doi.org/10.1021/ci00057a005
- 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:
- Returns:
Parsed molecules.
- Return type:
- Raises:
InvalidMoleculeError – If
on_error="raise"and a record fails to parse.FileNotFoundError – If
pathdoes not exist.
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
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
RDKit documentation: https://www.rdkit.org/docs/GettingStartedInPython.html
- 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.Noneentries are skipped.properties (
Optional[Dict[str,Sequence[Any]]]) – Extra SD data fields, one sequence per field aligned withmols(e.g.{"pIC50": [7.2, 6.4]}). Values are written withstr().kekulize (
bool) – Kekulize aromatic rings before writing (standard for MDL formats).
- Returns:
Number of records written.
- Return type:
- 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.Noneentries 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 becomeNone).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:
- Raises:
ValueError – If an
extrasequence 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
McKinney (2010). “Data Structures for Statistical Computing in Python.” Proc. 9th Python in Science Conf., 56-61. https://doi.org/10.25080/Majora-92bf1922-00a
RDKit documentation: https://www.rdkit.org/docs/
- 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_Nameproperty.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:
- Raises:
KeyError – If
smiles_columnis not a column ofdf.InvalidMoleculeError – If
on_error="raise"and a SMILES cannot be parsed.
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
Weininger (1988). “SMILES, a chemical language and information system. 1.” J. Chem. Inf. Comput. Sci., 28(1), 31-36. https://doi.org/10.1021/ci00057a005
RDKit documentation: https://www.rdkit.org/docs/
- 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:
smiles_column (
str) – Column holding SMILES strings.activity_column (
Optional[str]) – Column holding the target values. WhenNonethe returnedyisNone.sanitize (
bool) – Run RDKit sanitization on parsing.on_error (
str) – Handling of unparsable SMILES."none"keeps alignment betweenmols,yand the DataFrame rows.**read_csv_kwargs (
Any) – Forwarded topandas.read_csv().
- Return type:
- Returns:
mols (
listofrdkit.Chem.Mol) – Parsed molecules.y (
numpy.ndarrayorNone) – Target values whenactivity_columnis given.df (
pandas.DataFrame) – The raw table as read.
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
McKinney (2010). “Data Structures for Statistical Computing in Python.” Proc. 9th Python in Science Conf., 56-61. https://doi.org/10.25080/Majora-92bf1922-00a
- qsarkit.utils.get_logger(name=None)[source]¶
Return a logger inside the
qsarkitnamespace.- 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 atqsarkit.Nonereturns the package root logger.- Returns:
The requested logger. It has no handler of its own; records propagate to the
qsarkitroot logger, which carries aNullHandlerunlessconfigure_logging()was called.- Return type:
Examples
>>> from qsarkit.utils import get_logger >>> log = get_logger(__name__) >>> log.name.startswith("qsarkit") True
References
Python Software Foundation. “Logging HOWTO.” https://docs.python.org/3/howto/logging.html
- 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
qsarkitroot logger.This is an application-level convenience; libraries importing
qsarkitshould not call it. Calling it repeatedly replaces the previously installed qsarkit handler rather than stacking duplicates.- Parameters:
- Returns:
The configured
qsarkitroot logger.- Return type:
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
Python Software Foundation. “Logging HOWTO.” https://docs.python.org/3/howto/logging.html
- qsarkit.utils.check_mols(mols, allow_none=False, min_size=1)[source]¶
Validate and materialize an
Iterable[Mol].- Parameters:
- Returns:
The materialized list.
- Return type:
- Raises:
InvalidMoleculeError – If an element is not an RDKit
Mol(or isNonewhileallow_noneisFalse), or if fewer thanmin_sizemolecules 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:
y (
Optional[Sequence[Any]]) – Target values. IfNoneonly the molecules are validated and the returned target isNone(mirrorsfit(X, y=None)).allow_none (
bool) – Forwarded tocheck_mols().min_size (
int) – Minimum acceptable number of molecules.dtype (
Any) – NumPy dtype the target is cast to. PassNoneto keep the original dtype (useful for string class labels).
- Return type:
- Returns:
mols (
listofrdkit.Chem.Mol) – The validated molecules.y (
numpy.ndarrayorNone) – The target as a 1-D array, orNone.
- Raises:
InvalidMoleculeError – If the molecules are invalid.
ValueError – If
len(y) != len(mols)oryis not 1-D.
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:
Examples
>>> from qsarkit.utils import nm_to_molar >>> round(float(nm_to_molar(1000.0)), 12) 1e-06
References
Bento et al. (2014). “The ChEMBL bioactivity database: an update.” Nucleic Acids Res., 42, D1083-D1090. https://doi.org/10.1093/nar/gkt1031
- 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 inqsarkit.utils.constants.CONCENTRATION_TO_MOLAR).to_unit (
str) – Source and destination units. Supported keys:M,mM,uM,nM,pM,fM(plus the aliases inqsarkit.utils.constants.CONCENTRATION_TO_MOLAR).
- Returns:
The converted concentration(s).
- Return type:
- 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
Bento et al. (2014). “The ChEMBL bioactivity database: an update.” Nucleic Acids Res., 42, D1083-D1090. https://doi.org/10.1093/nar/gkt1031
- 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:
- Returns:
The p-scale activity value(s). Non-positive or non-finite inputs map to
nan.- Return type:
Examples
>>> from qsarkit.utils import to_pactivity >>> float(to_pactivity(1.0, "nM")) 9.0 >>> float(to_pactivity(1000.0, "nM")) 6.0
References
Kalliokoski et al. (2013). “Comparability of Mixed IC50 Data - A Statistical Analysis.” PLoS ONE, 8(4), e61007. https://doi.org/10.1371/journal.pone.0061007
Bento et al. (2014). “The ChEMBL bioactivity database: an update.” Nucleic Acids Res., 42, D1083-D1090. https://doi.org/10.1093/nar/gkt1031
- qsarkit.utils.from_pactivity(pvalue, unit='nM')[source]¶
Invert
to_pactivity(), returning a concentration.- Parameters:
- Returns:
Concentration(s) expressed in
unit.- Return type:
Examples
>>> from qsarkit.utils import from_pactivity >>> round(float(from_pactivity(9.0, "nM")), 6) 1.0
References
Kalliokoski et al. (2013). “Comparability of Mixed IC50 Data - A Statistical Analysis.” PLoS ONE, 8(4), e61007. https://doi.org/10.1371/journal.pone.0061007
- 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 * pXwithKthe association constant, valid when the reported p-value is apKd/pKi(an equilibrium dissociation constant). Applying it to apIC50is an approximation that ignores the Cheng-Prusoff correction.- Parameters:
- Returns:
Binding free energy, negative for favourable binding.
- Return type:
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