From 341171e5135aefd1ae8c4d6737d0eaacbe3320f7 Mon Sep 17 00:00:00 2001 From: riesben Date: Fri, 13 Sep 2024 17:12:15 +0200 Subject: [PATCH 01/24] adapting to new rdkit fingerprint generators. --- scikit_mol/fingerprints.py | 499 +++++++++++++++++++++++++++++++++++++ 1 file changed, 499 insertions(+) diff --git a/scikit_mol/fingerprints.py b/scikit_mol/fingerprints.py index 767bfc6..ef33aba 100644 --- a/scikit_mol/fingerprints.py +++ b/scikit_mol/fingerprints.py @@ -11,6 +11,11 @@ from rdkit.Chem import rdMHFPFingerprint from rdkit.Avalon import pyAvalonTools +from rdkit.Chem.rdFingerprintGenerator import (GetMorganGenerator, GetMorganFeatureAtomInvGen, + GetTopologicalTorsionGenerator, + GetAtomPairGenerator, + GetRDKitFPGenerator) + import numpy as np import pandas as pd from scipy.sparse import lil_matrix @@ -243,6 +248,9 @@ def __init__(self, minLength:int = 1, maxLength:int = 30, fromAtoms = 0, ignoreA self.nBitsPerEntry = nBitsPerEntry self.useCounts = useCounts + raise DeprecationWarning("TopologicalTorsionFingerprintTransformer will be replace by TopologicalTorsionFingerprintGeneratorTransformer, due to changes in RDKit!") + + def _mol2fp(self, mol): if self.useCounts: return rdMolDescriptors.GetHashedAtomPairFingerprint(mol, nBits=int(self.nBits), @@ -281,6 +289,8 @@ def __init__(self, targetSize:int = 4, fromAtoms = 0, ignoreAtoms = 0, atomInvar self.nBitsPerEntry = nBitsPerEntry self.nBits = nBits self.useCounts = useCounts + raise DeprecationWarning("TopologicalTorsionFingerprintTransformer will be replace by TopologicalTorsionFingerprintGeneratorTransformer, due to changes in RDKit!") + def _mol2fp(self, mol): if self.useCounts: @@ -478,6 +488,8 @@ def __init__(self, nBits=2048, radius=2, useChirality=False, useBondTypes=True, self.useBondTypes = useBondTypes self.useFeatures = useFeatures self.useCounts = useCounts + raise DeprecationWarning("MorganFingerprintTransformer will be replace by MorganGeneratorTransformer, due to changes in RDKit!") + def _mol2fp(self, mol): if self.useCounts: @@ -541,3 +553,490 @@ def parallel_helper(args): transformer = getattr(fingerprints, classname)(**parameters) return transformer._transform(X_mols) + +class FpsGeneratorTransformer(FpsTransformer): + + + def _fp2array(self, fp): + raise DeprecationWarning("Generators can directly return fingerprints") + + def _mol2fp(self, mol): + raise DeprecationWarning("use _mol2array") + + def __getstate__(self): + # Get the state of the parent class + state = super().__getstate__() + # Remove the unpicklable property from the state + state.pop("_fpgen", None) # fpgen is not picklable + return state + + def __setstate__(self, state): + # Restore the state of the parent class + super().__setstate__(state) + # Re-create the unpicklable property + self._generate_fp_generator() + + @abstractmethod + def _generate_fp_generator(self,*args, **kwargs): + raise NotImplementedError("_generate_fp_generator not implemented") + + @abstractmethod + def _transform_mol(self, mol) -> np.array: + """Generate numpy array descriptor from mol + + MUST BE OVERWRITTEN + """ + raise NotImplementedError("_transform_mol not implemented") + + +class MorganFPGeneratorTransformer(FpsGeneratorTransformer): + def __init__(self, nBits=2048, radius=2, useChirality=False, + useBondTypes=True, useFeatures=False, useCounts=False, + parallel: Union[bool, int] = False,): + """Transform RDKit mols into Count or bit-based hashed MorganFingerprints + + Parameters + ---------- + nBits : int, optional + Size of the hashed fingerprint, by default 2048 + radius : int, optional + Radius of the fingerprint, by default 2 + useChirality : bool, optional + Include chirality in calculation of the fingerprint keys, by default False + useBondTypes : bool, optional + Include bondtypes in calculation of the fingerprint keys, by default True + useFeatures : bool, optional + use chemical features, rather than atom-type in calculation of the fingerprint keys, by default False + useCounts : bool, optional + If toggled will create the count and not bit-based fingerprint, by default False + """ + super().__init__(parallel = parallel) + self._useFeatures = useFeatures + self._useCounts = useCounts + self._useBondTypes = useBondTypes + self._generate_fp_generator(useFeatures=useFeatures, radius=radius, nBits=nBits, + useChirality=useChirality, useBondTypes=useBondTypes) + + + def _generate_fp_generator(self, useFeatures:bool, radius:int, nBits:int, + useChirality:bool, useBondTypes:bool): + + if useFeatures: + atomInvariantsGenerator = GetMorganFeatureAtomInvGen() + else: + atomInvariantsGenerator = None + + self._fpgen = GetMorganGenerator(radius=radius, + fpSize=nBits, + includeChirality=useChirality, + useBondTypes=useBondTypes, + atomInvariantsGenerator=atomInvariantsGenerator, + ) + + @property + def radius(self): + return self._fpgen.GetOptions().radius + + @radius.setter + def radius(self, value:int): + self._fpgen.GetOptions().radius = value + + @property + def nBits(self): + return self._fpgen.GetOptions().fpSize + + @nBits.setter + def nBits(self, value:int): + self._fpgen.GetOptions().fpSize = value + + @property + def useChirality(self): + return self._fpgen.GetOptions().includeChirality + + @useChirality.setter + def useChirality(self, value:bool): + self._fpgen.GetOptions().includeChirality = value + + @property + def useFeatures(self): + return self._useFeatures + + @useFeatures.setter + def useFeatures(self, value:bool): + self._useFeatures = value + self._generate_fp_generator(useFeatures=self.useFeatures, radius=self.radius, nBits=self.nBits, + useChirality=self.useChirality, useBondTypes=self.useBondTypes) + + @property + def useBondTypes(self): + return self._useBondTypes + + @useBondTypes.setter + def useBondTypes(self, value:bool): + self._useBondTypes = value + self._generate_fp_generator(useFeatures=self.useFeatures, radius=self.radius, nBits=self.nBits, + useChirality=self.useChirality, useBondTypes=self.useBondTypes) + + @property + def useCounts(self): + return self._useCounts + + @useCounts.setter + def useCounts(self, value:bool): + self._useCounts = value + + def _transform_mol(self, mol) -> np.array: + if self.useCounts: + return self._fpgen.GetCountFingerprintAsNumPy(mol) + else: + return self._fpgen.GetFingerprintAsNumPy(mol) + + +class TopologicalTorsionFPGeneatorTransformer(FpsGeneratorTransformer): + def __init__(self, targetSize:int = 4, fromAtoms = None, ignoreAtoms = None, atomInvariants = None, confId=-1, + includeChirality:bool = False, nBitsPerEntry:int = 4, nBits=2048, + useCounts:bool=False, parallel: Union[bool, int] = False): + + super().__init__(parallel=parallel) + self._fromAtoms = fromAtoms + self._ignoreAtoms = ignoreAtoms + self._atomInvariants = atomInvariants + self._nBitsPerEntry = nBitsPerEntry + self._confId = confId + self._useCounts = useCounts + self._targetSize = targetSize + + self._generate_fp_generator(targetSize=targetSize, includeChirality=includeChirality, + nBits=nBits) + + @property + def useCounts(self): + return self._useCounts + + @useCounts.setter + def useCounts(self, value:bool): + self._useCounts = value + + @property + def confId(self): + return self._confId + + @confId.setter + def confId(self, value: int): + self._confId = value + + @property + def fromAtoms(self): + return self._fromAtoms + + @fromAtoms.setter + def fromAtoms(self, value: int): + self._fromAtoms = value + + @property + def ignoreAtoms(self): + return self._ignoreAtoms + + @ignoreAtoms.setter + def ignoreAtoms(self, value: int): + self._ignoreAtoms = value + + @property + def atomInvariants(self): + return self._atomInvariants + + @atomInvariants.setter + def atomInvariants(self, value: int): + self._atomInvariants = value + + @property + def nBits(self): + return self._fpgen.GetOptions().fpSize + + @nBits.setter + def nBits(self, value: int): + self._fpgen.GetOptions().fpSize = value + + @property + def nBitsPerEntry(self): + return self._nBitsPerEntry + + @nBitsPerEntry.setter + def nBitsPerEntry(self, value: int): + self._nBitsPerEntry = value + + @property + def includeChirality(self): + return self._fpgen.GetOptions().includeChirality + + @includeChirality.setter + def includeChirality(self, value:int): + self._fpgen.GetOptions().includeChirality = value + + @property + def targetSize(self): + return self._targetSize + + @targetSize.setter + def targetSize(self, value:int): + self._targetSize = value + self._generate_fp_generator(targetSize=value, + includeChirality=self.includeChirality, + nBits=self.nBits) + + def _generate_fp_generator(self, targetSize: int, includeChirality: bool, nBits: int): + self._fpgen = GetTopologicalTorsionGenerator(torsionAtomCount=targetSize, includeChirality=includeChirality, + fpSize=nBits) + + def _transform_mol(self, mol) -> np.array: + if self.useCounts: + return self._fpgen.GetCountFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self._ignoreAtoms, customAtomInvariants=self._atomInvariants) + else: + return self._fpgen.GetFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self._ignoreAtoms, customAtomInvariants=self._atomInvariants) + + +class AtomPairFPGeneratorTransformer(FpsGeneratorTransformer): + def __init__(self, minLength:int = 1, maxLength:int = 30, fromAtoms = None, ignoreAtoms = None, atomInvariants = None, + includeChirality:bool = False, use2D:bool = True, confId:int = -1, nBits=2048, nBitsPerEntry:int = 4, + useCounts:bool=False, parallel: Union[bool, int] = False,): + super().__init__(parallel = parallel) + self._useCounts= useCounts + self._confId = confId + self._fromAtoms = fromAtoms + self._ignoreAtoms = ignoreAtoms + self._atomInvariants = atomInvariants + self._minLength = minLength + self._maxLength = maxLength + + self._generate_fp_generator(minLength=minLength, maxLength=maxLength, + includeChirality=includeChirality, use2D=use2D, + nBits=nBits, nBitsPerEntry=nBitsPerEntry) + + @property + def useCounts(self): + return self._useCounts + + @useCounts.setter + def useCounts(self, value:bool): + self._useCounts = value + + @property + def confId(self): + return self._confId + + @confId.setter + def confId(self, value:int): + self._confId = value + + @property + def fromAtoms(self): + return self._fromAtoms + + @fromAtoms.setter + def fromAtoms(self, value:int): + self._fromAtoms = value + + @property + def ignoreAtoms(self): + return self._ignoreAtoms + + @ignoreAtoms.setter + def ignoreAtoms(self, value:int): + self._ignoreAtoms = value + + @property + def atomInvariants(self): + return self._atomInvariants + + @atomInvariants.setter + def atomInvariants(self, value:int): + self._atomInvariants = value + + @property + def minLength(self): + return self._minLength + + @minLength.setter + def minDistance(self, value: int): + self._minLength = value + self._generate_fp_generator(minLength=value, maxLength=self.maxLength, + includeChirality=self.includeChirality, use2D=self.use2D, + nBits=self.nBits, nBitsPerEntry=self.nBitsPerEntry) + + @property + def maxLength(self): + return self._maxLength + + @maxLength.setter + def maxLength(self, value: int): + self._maxLength = value + self._generate_fp_generator(minLength=self.minLength, maxLength=value, + includeChirality=self.includeChirality, use2D=self.use2D, + nBits=self.nBits, nBitsPerEntry=self.nBitsPerEntry) + + @property + def includeChirality(self): + return self._fpgen.GetOptions().includeChirality + + @includeChirality.setter + def includeChirality(self, value: bool): + self._fpgen.GetOptions().includeChirality = value + + @property + def use2D(self): + return self._fpgen.GetOptions().use2D + + @use2D.setter + def use2D(self, value: bool): + self._fpgen.GetOptions().use2D = value + + @property + def nBits(self): + return self._fpgen.GetOptions().fpSize + + @nBits.setter + def nBits(self, value: int): + self._fpgen.GetOptions().fpSize = value + + @property + def nBitsPerEntry(self): + return self._fpgen.GetOptions().numBitsPerFeature + + @nBitsPerEntry.setter + def nBitsPerEntry(self, value: int): + self._fpgen.GetOptions().numBitsPerFeature = value + + def _generate_fp_generator(self, minLength, maxLength, includeChirality, use2D, nBits, nBitsPerEntry): + self._fpgen = GetAtomPairGenerator(minDistance=minLength, maxDistance=maxLength, + includeChirality=includeChirality, + use2D=use2D, fpSize=nBits) + + def _transform_mol(self, mol) -> np.array: + if self.useCounts: + return self._fpgen.GetCountFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self._ignoreAtoms, customAtomInvariants=self._atomInvariants) + else: + return self._fpgen.GetFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self._ignoreAtoms, customAtomInvariants=self._atomInvariants) + + +class RDKitFPGeneratorTransformer(FpsGeneratorTransformer): + def __init__(self, minPath:int = 1, maxPath:int =7, useHs:bool = True, branchedPaths:bool = True, + useBondOrder:bool = True, countSimulation:bool = False, countBounds = None, + nBits:int = 2048, numBitsPerFeature:int = 2, + useCounts:bool = False, parallel: Union[bool, int] = False + ): + """Calculates the RDKit fingerprints + + Parameters + ---------- + minPath : int, optional + the minimum path length (in bonds) to be included, by default 1 + maxPath : int, optional + the maximum path length (in bonds) to be included, by default 7 + useHs : bool, optional + toggles inclusion of Hs in paths (if the molecule has explicit Hs), by default True + branchedPaths : bool, optional + toggles generation of branched subgraphs, not just linear paths, by default True + useBondOrder : bool, optional + toggles inclusion of bond orders in the path hashes, by default True + countSimulation : bool, optional + if set, use count simulation while generating the fingerprint, by default False + countBounds : _type_, optional + boundaries for count simulation, corresponding bit will be set if the count is higher than the number provided for that spot, by default None + nBits : int, optional + size of the generated fingerprint, does not affect the sparse versions, by default 2048 + numBitsPerFeature : int, optional + the number of bits set per path/subgraph found, by default 2 + """ + super().__init__(parallel = parallel) + self._useCounts = useCounts + self._countBounds = countBounds + self._generate_fp_generator( minPath=minPath, maxPath=maxPath, useHs=useHs, + branchedPaths=branchedPaths,useBondOrder=useBondOrder, + countSimulation=countSimulation, fpSize=nBits, + countBounds=countBounds, numBitsPerFeature=numBitsPerFeature) + + + @property + def nBits(self): + return self._fpgen.GetOptions().fpSize + @nBits.setter + def nBits(self, value: int): + self._fpgen.GetOptions().fpSize = value + @property + def minPath(self): + return self._fpgen.GetOptions().minPath + @minPath.setter + def minPath(self, value:int): + self._fpgen.GetOptions().minPath = value + @property + def maxPath(self): + return self._fpgen.GetOptions().maxPath + @maxPath.setter + def maxPath(self, value:int): + self._fpgen.GetOptions().maxPath = value + @property + def useHs(self): + return self._fpgen.GetOptions().useHs + @useHs.setter + def useHs(self, value:bool): + self._fpgen.GetOptions().useHs = value + @property + def branchedPaths(self): + return self._fpgen.GetOptions().branchedPaths + @branchedPaths.setter + def branchedPaths(self, value:int): + self._fpgen.GetOptions().branchedPaths = value + @property + def useBondOrder(self): + return self._fpgen.GetOptions().useBondOrder + @useBondOrder.setter + def useBondOrder(self, value:int): + self._fpgen.GetOptions().useBondOrder = value + @property + def numBitsPerFeature(self): + return self._fpgen.GetOptions().numBitsPerFeature + @numBitsPerFeature.setter + def numBitsPerFeature(self, value:int): + self._fpgen.GetOptions().numBitsPerFeature = value + @property + def countBounds(self): + return self._countBounds + @countBounds.setter + def countBounds(self, value:int): + self._countBounds = value + self._generate_fp_generator(minPath=self.minPath, maxPath=self.maxPath, useHs=self.useHs, + branchedPaths=self.branchedPaths,useBondOrder=self.useBondOrder, + countSimulation=self.countSimulation, fpSize=self.nBits, + countBounds=value, numBitsPerFeature=self.numBitsPerFeature) + + @property + def countSimulation(self): + return self._countBounds + @countSimulation.setter + def countSimulation(self, value: bool): + self._countSimulation=value + self._generate_fp_generator(minPath=self.minPath, maxPath=self.maxPath, useHs=self.useHs, + branchedPaths=self.branchedPaths,useBondOrder=self.useBondOrder, + countSimulation=value, fpSize=self.nBits, + countBounds=self.countBounds, numBitsPerFeature=self.numBitsPerFeature) + + @property + def useCounts(self): + return self._useCounts + @useCounts.setter + def useCounts(self, value:bool): + self._useCounts = value + + def _transform_mol(self, mol) -> np.array: + if self.useCounts: + return self._fpgen.GetCountFingerprintAsNumPy(mol) + else: + return self._fpgen.GetFingerprintAsNumPy(mol) + + def _generate_fp_generator(self, minPath, maxPath, useHs, branchedPaths, + useBondOrder, countSimulation, fpSize, countBounds, + numBitsPerFeature): + self._fpgen = GetRDKitFPGenerator(minPath=minPath, maxPath=maxPath, useHs=useHs, + branchedPaths=branchedPaths,useBondOrder=useBondOrder, + countSimulation=countSimulation, fpSize=fpSize, + countBounds=countBounds, numBitsPerFeature=numBitsPerFeature) From 8be105ad1c62247c94d7cd8c09bd92ed572c4c71 Mon Sep 17 00:00:00 2001 From: riesben Date: Sat, 14 Sep 2024 09:51:13 +0200 Subject: [PATCH 02/24] Deprecations warnings in transformers: raise->prints --- scikit_mol/fingerprints.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scikit_mol/fingerprints.py b/scikit_mol/fingerprints.py index ef33aba..4c63f8f 100644 --- a/scikit_mol/fingerprints.py +++ b/scikit_mol/fingerprints.py @@ -248,7 +248,8 @@ def __init__(self, minLength:int = 1, maxLength:int = 30, fromAtoms = 0, ignoreA self.nBitsPerEntry = nBitsPerEntry self.useCounts = useCounts - raise DeprecationWarning("TopologicalTorsionFingerprintTransformer will be replace by TopologicalTorsionFingerprintGeneratorTransformer, due to changes in RDKit!") + print("AtomPairFingerprintTransformer will be replace by AtomPairFPGeneratorTransformer, due to changes in RDKit!") + #raise DeprecationWarning("AtomPairFingerprintTransformer will be replace by AtomPairFPGeneratorTransformer, due to changes in RDKit!") def _mol2fp(self, mol): @@ -289,7 +290,8 @@ def __init__(self, targetSize:int = 4, fromAtoms = 0, ignoreAtoms = 0, atomInvar self.nBitsPerEntry = nBitsPerEntry self.nBits = nBits self.useCounts = useCounts - raise DeprecationWarning("TopologicalTorsionFingerprintTransformer will be replace by TopologicalTorsionFingerprintGeneratorTransformer, due to changes in RDKit!") + print("TopologicalTorsionFingerprintTransformer will be replace by TopologicalTorsionFPGeneatorTransformer, due to changes in RDKit!") + #raise DeprecationWarning("AtomPairFingerprintTransformer will be replace by AtomPairFPGeneratorTransformer, due to changes in RDKit!") def _mol2fp(self, mol): @@ -488,7 +490,9 @@ def __init__(self, nBits=2048, radius=2, useChirality=False, useBondTypes=True, self.useBondTypes = useBondTypes self.useFeatures = useFeatures self.useCounts = useCounts - raise DeprecationWarning("MorganFingerprintTransformer will be replace by MorganGeneratorTransformer, due to changes in RDKit!") + + print("MorganFingerprintTransformer will be replace by MorganGeneratorTransformer, due to changes in RDKit!") + #raise DeprecationWarning("MorganFingerprintTransformer will be replace by MorganFPGeneratorTransformer, due to changes in RDKit!") def _mol2fp(self, mol): @@ -574,7 +578,7 @@ def __setstate__(self, state): # Restore the state of the parent class super().__setstate__(state) # Re-create the unpicklable property - self._generate_fp_generator() + self._generate_fp_generator(**state) @abstractmethod def _generate_fp_generator(self,*args, **kwargs): @@ -614,6 +618,7 @@ def __init__(self, nBits=2048, radius=2, useChirality=False, self._useFeatures = useFeatures self._useCounts = useCounts self._useBondTypes = useBondTypes + self._generate_fp_generator(useFeatures=useFeatures, radius=radius, nBits=nBits, useChirality=useChirality, useBondTypes=useBondTypes) From 681a493e74fff8c374cbb984da5a55b1712aff74 Mon Sep 17 00:00:00 2001 From: riesben Date: Sat, 14 Sep 2024 12:40:07 +0200 Subject: [PATCH 03/24] minor class property fixes, most test wun now. Need to look into cloning and pickling. --- scikit_mol/fingerprints.py | 45 +++--- tests/test_fptransformersgenerator.py | 188 ++++++++++++++++++++++++++ 2 files changed, 209 insertions(+), 24 deletions(-) create mode 100644 tests/test_fptransformersgenerator.py diff --git a/scikit_mol/fingerprints.py b/scikit_mol/fingerprints.py index 4c63f8f..69ca8fd 100644 --- a/scikit_mol/fingerprints.py +++ b/scikit_mol/fingerprints.py @@ -2,6 +2,8 @@ from multiprocessing import Pool, get_context import multiprocessing import re +import inspect +from typing import Callable from typing import Union from rdkit import Chem from rdkit import DataStructs @@ -571,6 +573,8 @@ def __getstate__(self): # Get the state of the parent class state = super().__getstate__() # Remove the unpicklable property from the state + props = {k:v for k,v in inspect.getmembers(self) if not isinstance(v, Callable) and not k.startswith("_")} + state.update(props) state.pop("_fpgen", None) # fpgen is not picklable return state @@ -578,7 +582,8 @@ def __setstate__(self, state): # Restore the state of the parent class super().__setstate__(state) # Re-create the unpicklable property - self._generate_fp_generator(**state) + generatort_keys = inspect.signature(self._generate_fp_generator).parameters.keys() + self._generate_fp_generator(**{k:state["_"+k] if "_"+k in state else state[k] for k in generatort_keys}) @abstractmethod def _generate_fp_generator(self,*args, **kwargs): @@ -592,6 +597,14 @@ def _transform_mol(self, mol) -> np.array: """ raise NotImplementedError("_transform_mol not implemented") + @property + def fpSize(self): + return self.nBits + + #Scikit-Learn expects to be able to set fpSize directly on object via .set_params(), so this updates nBits used by the abstract class + @fpSize.setter + def fpSize(self, fpSize): + self.nBits = fpSize class MorganFPGeneratorTransformer(FpsGeneratorTransformer): def __init__(self, nBits=2048, radius=2, useChirality=False, @@ -699,18 +712,16 @@ def _transform_mol(self, mol) -> np.array: class TopologicalTorsionFPGeneatorTransformer(FpsGeneratorTransformer): def __init__(self, targetSize:int = 4, fromAtoms = None, ignoreAtoms = None, atomInvariants = None, confId=-1, - includeChirality:bool = False, nBitsPerEntry:int = 4, nBits=2048, + includeChirality:bool = False, nBits=2048, useCounts:bool=False, parallel: Union[bool, int] = False): super().__init__(parallel=parallel) self._fromAtoms = fromAtoms self._ignoreAtoms = ignoreAtoms self._atomInvariants = atomInvariants - self._nBitsPerEntry = nBitsPerEntry self._confId = confId self._useCounts = useCounts self._targetSize = targetSize - self._generate_fp_generator(targetSize=targetSize, includeChirality=includeChirality, nBits=nBits) @@ -762,14 +773,6 @@ def nBits(self): def nBits(self, value: int): self._fpgen.GetOptions().fpSize = value - @property - def nBitsPerEntry(self): - return self._nBitsPerEntry - - @nBitsPerEntry.setter - def nBitsPerEntry(self, value: int): - self._nBitsPerEntry = value - @property def includeChirality(self): return self._fpgen.GetOptions().includeChirality @@ -802,7 +805,7 @@ def _transform_mol(self, mol) -> np.array: class AtomPairFPGeneratorTransformer(FpsGeneratorTransformer): def __init__(self, minLength:int = 1, maxLength:int = 30, fromAtoms = None, ignoreAtoms = None, atomInvariants = None, - includeChirality:bool = False, use2D:bool = True, confId:int = -1, nBits=2048, nBitsPerEntry:int = 4, + includeChirality:bool = False, use2D:bool = True, confId:int = -1, nBits=2048, useCounts:bool=False, parallel: Union[bool, int] = False,): super().__init__(parallel = parallel) self._useCounts= useCounts @@ -815,7 +818,7 @@ def __init__(self, minLength:int = 1, maxLength:int = 30, fromAtoms = None, igno self._generate_fp_generator(minLength=minLength, maxLength=maxLength, includeChirality=includeChirality, use2D=use2D, - nBits=nBits, nBitsPerEntry=nBitsPerEntry) + nBits=nBits) @property def useCounts(self): @@ -862,11 +865,11 @@ def minLength(self): return self._minLength @minLength.setter - def minDistance(self, value: int): + def minLength(self, value: int): self._minLength = value self._generate_fp_generator(minLength=value, maxLength=self.maxLength, includeChirality=self.includeChirality, use2D=self.use2D, - nBits=self.nBits, nBitsPerEntry=self.nBitsPerEntry) + nBits=self.nBits) @property def maxLength(self): @@ -877,7 +880,7 @@ def maxLength(self, value: int): self._maxLength = value self._generate_fp_generator(minLength=self.minLength, maxLength=value, includeChirality=self.includeChirality, use2D=self.use2D, - nBits=self.nBits, nBitsPerEntry=self.nBitsPerEntry) + nBits=self.nBits) @property def includeChirality(self): @@ -907,11 +910,7 @@ def nBits(self, value: int): def nBitsPerEntry(self): return self._fpgen.GetOptions().numBitsPerFeature - @nBitsPerEntry.setter - def nBitsPerEntry(self, value: int): - self._fpgen.GetOptions().numBitsPerFeature = value - - def _generate_fp_generator(self, minLength, maxLength, includeChirality, use2D, nBits, nBitsPerEntry): + def _generate_fp_generator(self, minLength, maxLength, includeChirality, use2D, nBits): self._fpgen = GetAtomPairGenerator(minDistance=minLength, maxDistance=maxLength, includeChirality=includeChirality, use2D=use2D, fpSize=nBits) @@ -1013,7 +1012,6 @@ def countBounds(self, value:int): branchedPaths=self.branchedPaths,useBondOrder=self.useBondOrder, countSimulation=self.countSimulation, fpSize=self.nBits, countBounds=value, numBitsPerFeature=self.numBitsPerFeature) - @property def countSimulation(self): return self._countBounds @@ -1024,7 +1022,6 @@ def countSimulation(self, value: bool): branchedPaths=self.branchedPaths,useBondOrder=self.useBondOrder, countSimulation=value, fpSize=self.nBits, countBounds=self.countBounds, numBitsPerFeature=self.numBitsPerFeature) - @property def useCounts(self): return self._useCounts diff --git a/tests/test_fptransformersgenerator.py b/tests/test_fptransformersgenerator.py new file mode 100644 index 0000000..f11ea95 --- /dev/null +++ b/tests/test_fptransformersgenerator.py @@ -0,0 +1,188 @@ +import pickle +import tempfile +import pytest +import numpy as np +from fixtures import mols_list, smiles_list, mols_container, smiles_container, fingerprint, chiral_smiles_list, chiral_mols_list +from sklearn import clone + +from scikit_mol.fingerprints import (MorganFPGeneratorTransformer, + RDKitFPGeneratorTransformer, + AtomPairFPGeneratorTransformer, + TopologicalTorsionFPGeneatorTransformer, + ) + +test_transformers = [MorganFPGeneratorTransformer, RDKitFPGeneratorTransformer, + AtomPairFPGeneratorTransformer, TopologicalTorsionFPGeneatorTransformer] + + +@pytest.mark.parametrize("transformer_class", test_transformers) +def test_fpstransformer_fp2array(transformer_class, fingerprint): + transformer = transformer_class() + + with pytest.raises(DeprecationWarning, match='Generators can directly return fingerprints'): + fp = transformer._fp2array(fingerprint) + + +@pytest.mark.parametrize("transformer_class", test_transformers) +def test_fpstransformer_transform_mol(transformer_class, mols_list): + transformer = transformer_class() + + fp = transformer._transform_mol(mols_list[0]) + #See that fp is the correct type, shape and bit count + assert(type(fp) == type(np.array([0]))) + assert(fp.shape == (2048,)) + + if isinstance(transformer, RDKitFPGeneratorTransformer): + assert(fp.sum() == 104) + elif isinstance(transformer, AtomPairFPGeneratorTransformer): + assert (fp.sum() == 32) + elif isinstance(transformer, TopologicalTorsionFPGeneatorTransformer): + assert (fp.sum() == 12) + elif isinstance(transformer, MorganFPGeneratorTransformer): + assert (fp.sum() == 14) + else: + raise NotImplementedError("missing Assert") + +@pytest.mark.parametrize("transformer_class", test_transformers) +def test_clonability(transformer_class): + transformer = transformer_class() + + params = transformer.get_params() + t2 = clone(transformer) + params_2 = t2.get_params() + #Parameters of cloned transformers should be the same + assert all([ params[key] == params_2[key] for key in params.keys()]) + #Cloned transformers should not be the same object + assert t2 != transformer + +@pytest.mark.parametrize("transformer_class", test_transformers) +def test_set_params(transformer_class): + transformer = transformer_class() + params = transformer.get_params() + #change extracted dictionary + params['nBits'] = 4242 + #change params in transformer + transformer.set_params(nBits = 4242) + # get parameters as dictionary and assert that it is the same + params_2 = transformer.get_params() + assert all([ params[key] == params_2[key] for key in params.keys()]) + +@pytest.mark.parametrize("transformer_class", test_transformers) +def test_transform(mols_container, transformer_class): + transformer = transformer_class() + #Test the different transformers + params = transformer.get_params() + fps = transformer.transform(mols_container) + #Assert that the same length of input and output + assert len(fps) == len(mols_container) + + fpsize = params['nBits'] + + assert len(fps[0]) == fpsize + +@pytest.mark.parametrize("transformer_class", test_transformers) +def test_transform_parallel(mols_container, transformer_class): + transformer = transformer_class() + #Test the different transformers + transformer.set_params(parallel=True) + params = transformer.get_params() + fps = transformer.transform(mols_container) + #Assert that the same length of input and output + assert len(fps) == len(mols_container) + + fpsize = params['nBits'] + assert len(fps[0]) == fpsize + + +@pytest.mark.parametrize("transformer_class", test_transformers) +def test_picklable(transformer_class): + #Test the different transformers + transformer = transformer_class() + p = transformer.get_params() + + with tempfile.NamedTemporaryFile() as f: + pickle.dump(transformer, f) + f.seek(0) + t2 = pickle.load(f) + print(p) + print(vars(transformer)) + print(vars(t2)) + assert(transformer.get_params() == t2.get_params()) + + +@pytest.mark.parametrize("transfomer", test_transformers) +def assert_transformer_set_params(transfomer, new_params, mols_list): + default_params = transfomer().get_params() + + for key in new_params.keys(): + tr = transfomer() + params = tr.get_params() + params[key] = new_params[key] + + fps_default = tr.transform(mols_list) + + tr.set_params(**params) + new_tr = transfomer(**params) + fps_reset_params = tr.transform(mols_list) + fps_init_new_params = new_tr.transform(mols_list) + + # Now fp_default should not be the same as fp_reset_params + + assert ~np.all([np.array_equal(fp_default, fp_reset_params) for fp_default, fp_reset_params in zip(fps_default, fps_reset_params)]), f"Assertation error, FP appears the same, although the {key} should be changed from {default_params[key]} to {params[key]}" + # fp_reset_params and fp_init_new_params should however be the same + assert np.all([np.array_equal(fp_init_new_params, fp_reset_params) for fp_init_new_params, fp_reset_params in zip(fps_init_new_params, fps_reset_params)]) , f"Assertation error, FP appears to be different, although the {key} should be changed back as well as initialized to {params[key]}" + + +def test_morgan_set_params(chiral_mols_list): + new_params = {'nBits': 1024, + 'radius': 1, + 'useBondTypes': False,# TODO, why doesn't this change the FP? + 'useChirality': True, + 'useCounts': True, + 'useFeatures': True} + + assert_transformer_set_params(MorganFPGeneratorTransformer, new_params, chiral_mols_list) + + +def test_atompairs_set_params(chiral_mols_list): + new_params = { + #'atomInvariants': 1, + #'confId': -1, + #'fromAtoms': 1, + #'ignoreAtoms': 0, + 'includeChirality': True, + 'maxLength': 3, + 'minLength': 3, + 'nBits': 1024, + #'nBitsPerEntry': 3, #Todo: not setable with the generators? + #'use2D': True, #TODO, understand why this can't be set different + 'useCounts': True} + + assert_transformer_set_params(AtomPairFPGeneratorTransformer, new_params, chiral_mols_list) + + +def test_topologicaltorsion_set_params(chiral_mols_list): + new_params = {#'atomInvariants': 0, + #'fromAtoms': 0, + #'ignoreAtoms': 0, + #'includeChirality': True, #TODO, figure out why this setting seems to give same FP wheter toggled or not + 'nBits': 1024, + #'nBitsPerEntry': 3, #Todo: not setable with the generators? + 'targetSize': 5, + 'useCounts': True} + + assert_transformer_set_params(TopologicalTorsionFPGeneatorTransformer, new_params, chiral_mols_list) + +def test_RDKitFPTransformer(chiral_mols_list): + new_params = {#'atomInvariantsGenerator': None, + #'branchedPaths': False, + #'countBounds': 0, #TODO: What does this do? + 'countSimulation': True, + 'nBits': 1024, + 'maxPath': 3, + 'minPath': 2, + 'numBitsPerFeature': 3, + 'useBondOrder': False, #TODO, why doesn't this change the FP? + #'useHs': False, #TODO, why doesn't this change the FP? + } + assert_transformer_set_params(RDKitFPGeneratorTransformer, new_params, chiral_mols_list) From c812214103036b0596f82d15961894e197bfb28d Mon Sep 17 00:00:00 2001 From: riesben Date: Mon, 16 Sep 2024 23:14:01 +0200 Subject: [PATCH 04/24] fixes for bugs --- scikit_mol/fingerprints.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scikit_mol/fingerprints.py b/scikit_mol/fingerprints.py index 69ca8fd..66d6c51 100644 --- a/scikit_mol/fingerprints.py +++ b/scikit_mol/fingerprints.py @@ -572,9 +572,8 @@ def _mol2fp(self, mol): def __getstate__(self): # Get the state of the parent class state = super().__getstate__() + state.update(self.get_params()) # Remove the unpicklable property from the state - props = {k:v for k,v in inspect.getmembers(self) if not isinstance(v, Callable) and not k.startswith("_")} - state.update(props) state.pop("_fpgen", None) # fpgen is not picklable return state @@ -583,7 +582,8 @@ def __setstate__(self, state): super().__setstate__(state) # Re-create the unpicklable property generatort_keys = inspect.signature(self._generate_fp_generator).parameters.keys() - self._generate_fp_generator(**{k:state["_"+k] if "_"+k in state else state[k] for k in generatort_keys}) + params = {k:state["_"+k] if "_"+k in state else state[k] for k in generatort_keys} + self._generate_fp_generator(**params) @abstractmethod def _generate_fp_generator(self,*args, **kwargs): From 523b1905bcf1de2a7e9d5f8c59dc1482607fb338 Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Sat, 26 Oct 2024 15:56:13 +0200 Subject: [PATCH 05/24] updated readmes --- CONTRIBUTION.md | 8 +++++--- README.md | 7 ++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 3e39632..1b9aa61 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -5,7 +5,7 @@ Thanks for your interest in contributing to the project. Please read on in the s ## Slack channel We have a slack channel for communication, ask for an invite: esbenbjerrum+scikit_mol@gmail.com -It's not really active and Slack wan't to be paid now. Maybe we can use Discord instead. +It's not really active and Slack wan't to be paid now. Maybe we can use Discord instead as slack is now deleting old threads. ## Installation @@ -22,12 +22,13 @@ The projects transformers subclasses the BaseEstimator and Transformer mixin cla - The arguments accepted by **init** should all be keyword arguments with a default value. - Every keyword argument accepted by **init** should correspond to an attribute on the instance. -- - There should be no logic, not even input validation, and the parameters should not be changed. +- - There should be no logic, not even input validation, and the parameters should not be changed inside the **init** function. Scikit-learn classes depends on this in order to for e.g. the .get_params(), .set_params(), cloning abilities and representation rendering to work. +- With the new error handling, falsy objects need to return masked arrays or arrays with np.nan (for float dtype) ### Tips -- We have observed that some external tools used "exotic" types such at np.int64 when doing hyperparameter tuning. It is thus necessary to cast to standard types before making calls to rdkit functions. This behaviour is tested in the test_parameter_types test +- We have observed that some external tools used "exotic" types such at np.int64 when doing hyperparameter tuning. It is thus necessary do defensive programming to cast parameters to standard types before making calls to rdkit functions. This behaviour is tested in the test_parameter_types test - @property getters and setters can be used if additional logic are needed when setting the attributes from the keywords while at the same time adhering to the sklearn requisites. @@ -48,6 +49,7 @@ parameters and output of methods should preferably be using typehints ## Testing New transformer classes should be added to the pytest tests in the tests directory. A lot of tests are made general, and tests aspects of the transformers that are needed for sklearn compliance or other features. The transformer is then added to a fixture and can be added to the lists of transformer objects that are run by these test. Specific tests may also be necessary to set up. As exampe the assert_transformer_set_params needs a list of non-default parameters in order to set the set_params functionality of the object. +Scikit-Learn has a check_estimator that we should strive to get to work, some classes of scikit-mol currently does not pass all tests. ## Notebooks diff --git a/README.md b/README.md index d59717d..7c6255f 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,12 @@ There are a collection of notebooks in the notebooks directory which demonstrate We also put a software note on ChemRxiv. [https://doi.org/10.26434/chemrxiv-2023-fzqwd](https://doi.org/10.26434/chemrxiv-2023-fzqwd) -## Contributing +## Roadmap and Contributing + +_Help wanted!_ Are you a PhD student that want a "side-quest" to procrastinate your thesis writing or are you simply interested in computational chemistry, cheminformatics or simply with an interest in QSAR modelling, Python Programming open-source software? Do you want to learn more about machine learning with Scikit-Learn? Or do you use scikit-mol for your current work and would like to pay a little back to the project and see it improved as well? +With a little bit of help, this project can be improved much faster! Reach to me (Esben), for a discussion about how we can proceed. + +Currently we are working on fixing some deprecation warnings, its not the most exciting work, but it's important to maintain a little. Later on we need to go over the scikit-learn compatibility and update to some of their newer features on their estimator classes. We're also brewing on some feature enhancements and tests, such as new fingerprints and a more versatile standardizer. There are more information about how to contribute to the project in [CONTRIBUTION.md](https://github.com/EBjerrum/scikit-mol/CONTRIBUTION.md) From fdd8624ece60a5fd0a245eb67bed042b17e91ca6 Mon Sep 17 00:00:00 2001 From: riesben Date: Thu, 14 Nov 2024 22:32:53 +0100 Subject: [PATCH 06/24] Remodelling transformers: - nBits->fpSize - remove properties / overwrite setattr - adapt tests. --- scikit_mol/fingerprints.py | 550 +++++++------------------- tests/test_fptransformers.py | 55 +-- tests/test_fptransformersgenerator.py | 16 +- tests/test_safeinferencemode.py | 4 +- tests/test_transformers.py | 7 +- 5 files changed, 173 insertions(+), 459 deletions(-) diff --git a/scikit_mol/fingerprints.py b/scikit_mol/fingerprints.py index ace9795..a96e65b 100644 --- a/scikit_mol/fingerprints.py +++ b/scikit_mol/fingerprints.py @@ -56,7 +56,7 @@ def _get_column_prefix(self) -> str: return "fp" def _get_n_digits_column_suffix(self) -> int: - return len(str(self.nBits)) + return len(str(self.fpSize)) def get_display_feature_names_out(self, input_features=None): """Get feature names for display purposes @@ -68,7 +68,7 @@ def get_display_feature_names_out(self, input_features=None): prefix = self._get_column_prefix() n_digits = self._get_n_digits_column_suffix() return np.array( - [f"{prefix}_{str(i).zfill(n_digits)}" for i in range(1, self.nBits + 1)] + [f"{prefix}_{str(i).zfill(n_digits)}" for i in range(1, self.fpSize + 1)] ) def get_feature_names_out(self, input_features=None): @@ -78,7 +78,7 @@ def get_feature_names_out(self, input_features=None): to get the column names of the transformed dataframe. """ prefix = self._get_column_prefix() - return np.array([f"{prefix}_{i}" for i in range(1, self.nBits + 1)]) + return np.array([f"{prefix}_{i}" for i in range(1, self.fpSize + 1)]) @abstractmethod def _mol2fp(self, mol): @@ -90,11 +90,11 @@ def _mol2fp(self, mol): def _fp2array(self, fp): if fp: - arr = np.zeros((self.nBits,), dtype=self.dtype) + arr = np.zeros((self.fpSize,), dtype=self.dtype) DataStructs.ConvertToNumpyArray(fp, arr) return arr else: - return np.ma.masked_all((self.nBits,), dtype=self.dtype) + return np.ma.masked_all((self.fpSize,), dtype=self.dtype) def _transform_mol(self, mol): if not mol and self.safe_inference_mode: @@ -120,16 +120,17 @@ def _transform(self, X): if self.safe_inference_mode: # Use the new method with masked arrays if we're in safe inference mode arrays = [self._transform_mol(mol) for mol in X] + print(arrays) return np.ma.stack(arrays) else: # Use the original, faster method if we're not in safe inference mode - arr = np.zeros((len(X), self.nBits), dtype=self.dtype) + arr = np.zeros((len(X), self.fpSize), dtype=self.dtype) for i, mol in enumerate(X): arr[i, :] = self._transform_mol(mol) return arr def _transform_sparse(self, X): - arr = np.zeros((len(X), self.nBits), dtype=self.dtype) + arr = np.zeros((len(X), self.fpSize), dtype=self.dtype) for i, mol in enumerate(X): arr[i, :] = self._transform_mol(mol) @@ -189,6 +190,7 @@ def __init__( parallel: Union[bool, int] = False, safe_inference_mode: bool = False, dtype: np.dtype = np.int8, + fpSize=167, ): """MACCS keys fingerprinter calculates the 167 fixed MACCS keys @@ -196,19 +198,23 @@ def __init__( super().__init__( parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype ) - self.nBits = 167 + if fpSize != 167: + raise ValueError( + "fpSize can only be 167, matching the number of defined MACCS keys!" + ) + self._fpSize = fpSize @property - def nBits(self): - return self._nBits + def fpSize(self): + return self._fpSize - @nBits.setter - def nBits(self, nBits): - if nBits != 167: + @fpSize.setter + def fpSize(self, fpSize): + if fpSize != 167: raise ValueError( - "nBits can only be 167, matching the number of defined MACCS keys!" + "fpSize can only be 167, matching the number of defined MACCS keys!" ) - self._nBits = nBits + self._fpSize = fpSize def _mol2fp(self, mol): return rdMolDescriptors.GetMACCSKeysFingerprint(mol) @@ -270,14 +276,6 @@ def __init__( self.numBitsPerFeature = numBitsPerFeature self.atomInvariantsGenerator = atomInvariantsGenerator - @property - def fpSize(self): - return self.nBits - - # Scikit-Learn expects to be able to set fpSize directly on object via .set_params(), so this updates nBits used by the abstract class - @fpSize.setter - def fpSize(self, fpSize): - self.nBits = fpSize def _mol2fp(self, mol): generator = rdFingerprintGenerator.GetRDKitFPGenerator( @@ -307,7 +305,7 @@ def __init__( includeChirality: bool = False, use2D: bool = True, confId: int = -1, - nBits=2048, + fpSize=2048, useCounts: bool = False, parallel: Union[bool, int] = False, safe_inference_mode: bool = False, @@ -324,7 +322,7 @@ def __init__( self.includeChirality = includeChirality self.use2D = use2D self.confId = confId - self.nBits = nBits + self.fpSize = fpSize self.nBitsPerEntry = nBitsPerEntry self.useCounts = useCounts @@ -336,7 +334,7 @@ def _mol2fp(self, mol): if self.useCounts: return rdMolDescriptors.GetHashedAtomPairFingerprint( mol, - nBits=int(self.nBits), + nBits=int(self.fpSize), minLength=int(self.minLength), maxLength=int(self.maxLength), fromAtoms=self.fromAtoms, @@ -349,7 +347,7 @@ def _mol2fp(self, mol): else: return rdMolDescriptors.GetHashedAtomPairFingerprintAsBitVect( mol, - nBits=int(self.nBits), + nBits=int(self.fpSize), minLength=int(self.minLength), maxLength=int(self.maxLength), fromAtoms=self.fromAtoms, @@ -371,7 +369,7 @@ def __init__( atomInvariants=0, includeChirality: bool = False, nBitsPerEntry: int = 4, - nBits=2048, + fpSize=2048, useCounts: bool = False, parallel: Union[bool, int] = False, safe_inference_mode: bool = False, @@ -386,7 +384,7 @@ def __init__( self.atomInvariants = atomInvariants self.includeChirality = includeChirality self.nBitsPerEntry = nBitsPerEntry - self.nBits = nBits + self.fpSize = fpSize self.useCounts = useCounts print("TopologicalTorsionFingerprintTransformer will be replace by TopologicalTorsionFPGeneatorTransformer, due to changes in RDKit!") #raise DeprecationWarning("AtomPairFingerprintTransformer will be replace by AtomPairFPGeneratorTransformer, due to changes in RDKit!") @@ -396,7 +394,7 @@ def _mol2fp(self, mol): if self.useCounts: return rdMolDescriptors.GetHashedTopologicalTorsionFingerprint( mol, - nBits=int(self.nBits), + nBits=int(self.fpSize), targetSize=int(self.targetSize), fromAtoms=self.fromAtoms, ignoreAtoms=self.ignoreAtoms, @@ -406,7 +404,7 @@ def _mol2fp(self, mol): else: return rdMolDescriptors.GetHashedTopologicalTorsionFingerprintAsBitVect( mol, - nBits=int(self.nBits), + nBits=int(self.fpSize), targetSize=int(self.targetSize), fromAtoms=self.fromAtoms, ignoreAtoms=self.ignoreAtoms, @@ -424,7 +422,7 @@ def __init__( isomeric: bool = False, kekulize: bool = False, min_radius: int = 1, - n_permutations: int = 2048, + fpSize: int = 2048, seed: int = 42, parallel: Union[bool, int] = False, safe_inference_mode: bool = False, @@ -440,7 +438,7 @@ def __init__( isomeric (bool, optional): Whether the isomeric SMILES to be considered. Defaults to False. kekulize (bool, optional): Whether or not to kekulize the extracted SMILES. Defaults to False. min_radius (int, optional): The minimum radius that is used to extract n-gram. Defaults to 1. - n_permutations (int, optional): The number of permutations used for hashing. Defaults to 0, + fpSize (int, optional): The number of permutations used for hashing. Defaults to 2048, this is effectively the length of the FP seed (int, optional): The value used to seed numpy.random. Defaults to 0. """ @@ -453,7 +451,7 @@ def __init__( self.kekulize = kekulize self.min_radius = min_radius # Set the .n_permutations and .seed without creating the encoder twice - self._n_permutations = n_permutations + self.fpSize = fpSize self._seed = seed # create the encoder instance self._recreate_encoder() @@ -482,7 +480,7 @@ def _fp2array(self, fp): def _recreate_encoder(self): self.mhfp_encoder = rdMHFPFingerprint.MHFPEncoder( - self._n_permutations, self._seed + self.fpSize, self._seed ) @property @@ -497,19 +495,14 @@ def seed(self, seed): @property def n_permutations(self): - return self._n_permutations + return self.fpSize @n_permutations.setter def n_permutations(self, n_permutations): - self._n_permutations = n_permutations + self.fpSize = n_permutations # each time the n_permutations parameter is modified refresh an instance of the encoder self._recreate_encoder() - @property - def nBits(self): - # to be compliant with the requirement of the base class - return self._n_permutations - class SECFingerprintTransformer(FpsTransformer): # https://jcheminf.biomedcentral.com/articles/10.1186/s13321-018-0321-8 @@ -520,7 +513,7 @@ def __init__( isomeric: bool = False, kekulize: bool = False, min_radius: int = 1, - length: int = 2048, + fpSize: int = 2048, n_permutations: int = 0, seed: int = 0, parallel: Union[bool, int] = False, @@ -535,7 +528,7 @@ def __init__( isomeric (bool, optional): Whether the isomeric SMILES to be considered. Defaults to False. kekulize (bool, optional): Whether or not to kekulize the extracted SMILES. Defaults to False. min_radius (int, optional): The minimum radius that is used to extract n-gram. Defaults to 1. - length (int, optional): The length of the folded fingerprint. Defaults to 2048. + fpSize (int, optional): The length of the folded fingerprint. Defaults to 2048. n_permutations (int, optional): The number of permutations used for hashing. Defaults to 0. seed (int, optional): The value used to seed numpy.random. Defaults to 0. """ @@ -547,7 +540,7 @@ def __init__( self.isomeric = isomeric self.kekulize = kekulize self.min_radius = min_radius - self.length = length + self.fpSize = fpSize # Set the .n_permutations and seed without creating the encoder twice self._n_permutations = n_permutations self._seed = seed @@ -604,15 +597,15 @@ def n_permutations(self, n_permutations): self._recreate_encoder() @property - def nBits(self): + def length(self): # to be compliant with the requirement of the base class - return self.length + return self.fpSize class MorganFingerprintTransformer(FpsTransformer): def __init__( self, - nBits=2048, + fpSize=2048, radius=2, useChirality=False, useBondTypes=True, @@ -626,7 +619,7 @@ def __init__( Parameters ---------- - nBits : int, optional + fpSize : int, optional Size of the hashed fingerprint, by default 2048 radius : int, optional Radius of the fingerprint, by default 2 @@ -642,7 +635,7 @@ def __init__( super().__init__( parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype ) - self.nBits = nBits + self.fpSize = fpSize self.radius = radius self.useChirality = useChirality self.useBondTypes = useBondTypes @@ -658,7 +651,7 @@ def _mol2fp(self, mol): return rdMolDescriptors.GetHashedMorganFingerprint( mol, int(self.radius), - nBits=int(self.nBits), + nBits=int(self.fpSize), useFeatures=bool(self.useFeatures), useChirality=bool(self.useChirality), useBondTypes=bool(self.useBondTypes), @@ -667,7 +660,7 @@ def _mol2fp(self, mol): return rdMolDescriptors.GetMorganFingerprintAsBitVect( mol, int(self.radius), - nBits=int(self.nBits), + nBits=int(self.fpSize), useFeatures=bool(self.useFeatures), useChirality=bool(self.useChirality), useBondTypes=bool(self.useBondTypes), @@ -678,7 +671,7 @@ class AvalonFingerprintTransformer(FpsTransformer): # Fingerprint from the Avalon toolkeit, https://doi.org/10.1021/ci050413p def __init__( self, - nBits: int = 512, + fpSize: int = 512, isQuery: bool = False, resetVect: bool = False, bitFlags: int = 15761407, @@ -691,7 +684,7 @@ def __init__( Parameters ---------- - nBits : int, optional + fpSize : int, optional Size of the fingerprint, by default 512 isQuery : bool, optional use the fingerprint for a query structure, by default False @@ -705,7 +698,7 @@ def __init__( super().__init__( parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype ) - self.nBits = nBits + self.fpSize = fpSize self.isQuery = isQuery self.resetVect = resetVect self.bitFlags = bitFlags @@ -715,14 +708,14 @@ def _mol2fp(self, mol): if self.useCounts: return pyAvalonTools.GetAvalonCountFP( mol, - nBits=int(self.nBits), + nBits=int(self.fpSize), isQuery=bool(self.isQuery), bitFlags=int(self.bitFlags), ) else: return pyAvalonTools.GetAvalonFP( mol, - nBits=int(self.nBits), + nBits=int(self.fpSize), isQuery=bool(self.isQuery), resetVect=bool(self.resetVect), bitFlags=int(self.bitFlags), @@ -740,7 +733,7 @@ def parallel_helper(args): return transformer._transform(X_mols) class FpsGeneratorTransformer(FpsTransformer): - + _regenerate_on_properties = () def _fp2array(self, fp): raise DeprecationWarning("Generators can directly return fingerprints") @@ -761,11 +754,19 @@ def __setstate__(self, state): super().__setstate__(state) # Re-create the unpicklable property generatort_keys = inspect.signature(self._generate_fp_generator).parameters.keys() - params = {k:state["_"+k] if "_"+k in state else state[k] for k in generatort_keys} - self._generate_fp_generator(**params) + params = [setattr(self, k, state["_"+k]) if "_"+k in state else setattr(self, k, state[k]) for k in generatort_keys] + self._generate_fp_generator() + + def __setattr__(self, name: str, value): + super().__setattr__(name, value) + if ( + not hasattr(self, "_initializing") + and name in self._regenerate_on_properties + ): + self._generate_fp_generator() @abstractmethod - def _generate_fp_generator(self,*args, **kwargs): + def _generate_fp_generator(self): raise NotImplementedError("_generate_fp_generator not implemented") @abstractmethod @@ -776,24 +777,18 @@ def _transform_mol(self, mol) -> np.array: """ raise NotImplementedError("_transform_mol not implemented") - @property - def fpSize(self): - return self.nBits - - #Scikit-Learn expects to be able to set fpSize directly on object via .set_params(), so this updates nBits used by the abstract class - @fpSize.setter - def fpSize(self, fpSize): - self.nBits = fpSize class MorganFPGeneratorTransformer(FpsGeneratorTransformer): - def __init__(self, nBits=2048, radius=2, useChirality=False, + _regenerate_on_properties = ("radius", "fpSize", "useChirality", "useFeatures", "useBondTypes") + + def __init__(self, fpSize=2048, radius=2, useChirality=False, useBondTypes=True, useFeatures=False, useCounts=False, - parallel: Union[bool, int] = False,): + parallel: Union[bool, int] = False, ): """Transform RDKit mols into Count or bit-based hashed MorganFingerprints Parameters ---------- - nBits : int, optional + fpsize : int, optional Size of the hashed fingerprint, by default 2048 radius : int, optional Radius of the fingerprint, by default 2 @@ -806,82 +801,34 @@ def __init__(self, nBits=2048, radius=2, useChirality=False, useCounts : bool, optional If toggled will create the count and not bit-based fingerprint, by default False """ + + self._initializing = True super().__init__(parallel = parallel) - self._useFeatures = useFeatures - self._useCounts = useCounts - self._useBondTypes = useBondTypes + self.fpSize = fpSize + self.radius = radius + self.useChirality = useChirality + self.useFeatures = useFeatures + self.useCounts = useCounts + self.useBondTypes = useBondTypes - self._generate_fp_generator(useFeatures=useFeatures, radius=radius, nBits=nBits, - useChirality=useChirality, useBondTypes=useBondTypes) + self._generate_fp_generator() + delattr(self, "_initializing") - def _generate_fp_generator(self, useFeatures:bool, radius:int, nBits:int, - useChirality:bool, useBondTypes:bool): + def _generate_fp_generator(self): - if useFeatures: + if self.useFeatures: atomInvariantsGenerator = GetMorganFeatureAtomInvGen() else: atomInvariantsGenerator = None - self._fpgen = GetMorganGenerator(radius=radius, - fpSize=nBits, - includeChirality=useChirality, - useBondTypes=useBondTypes, + self._fpgen = GetMorganGenerator(radius=self.radius, + fpSize=self.fpSize, + includeChirality=self.useChirality, + useBondTypes=self.useBondTypes, atomInvariantsGenerator=atomInvariantsGenerator, ) - @property - def radius(self): - return self._fpgen.GetOptions().radius - - @radius.setter - def radius(self, value:int): - self._fpgen.GetOptions().radius = value - - @property - def nBits(self): - return self._fpgen.GetOptions().fpSize - - @nBits.setter - def nBits(self, value:int): - self._fpgen.GetOptions().fpSize = value - - @property - def useChirality(self): - return self._fpgen.GetOptions().includeChirality - - @useChirality.setter - def useChirality(self, value:bool): - self._fpgen.GetOptions().includeChirality = value - - @property - def useFeatures(self): - return self._useFeatures - - @useFeatures.setter - def useFeatures(self, value:bool): - self._useFeatures = value - self._generate_fp_generator(useFeatures=self.useFeatures, radius=self.radius, nBits=self.nBits, - useChirality=self.useChirality, useBondTypes=self.useBondTypes) - - @property - def useBondTypes(self): - return self._useBondTypes - - @useBondTypes.setter - def useBondTypes(self, value:bool): - self._useBondTypes = value - self._generate_fp_generator(useFeatures=self.useFeatures, radius=self.radius, nBits=self.nBits, - useChirality=self.useChirality, useBondTypes=self.useBondTypes) - - @property - def useCounts(self): - return self._useCounts - - @useCounts.setter - def useCounts(self, value:bool): - self._useCounts = value - def _transform_mol(self, mol) -> np.array: if self.useCounts: return self._fpgen.GetCountFingerprintAsNumPy(mol) @@ -890,221 +837,81 @@ def _transform_mol(self, mol) -> np.array: class TopologicalTorsionFPGeneatorTransformer(FpsGeneratorTransformer): + _regenerate_on_properties = ("fpSize", "includeChirality", "targetSize") + def __init__(self, targetSize:int = 4, fromAtoms = None, ignoreAtoms = None, atomInvariants = None, confId=-1, - includeChirality:bool = False, nBits=2048, + includeChirality:bool = False, fpSize:int=2048, useCounts:bool=False, parallel: Union[bool, int] = False): + self._initializing = True super().__init__(parallel=parallel) - self._fromAtoms = fromAtoms - self._ignoreAtoms = ignoreAtoms - self._atomInvariants = atomInvariants - self._confId = confId - self._useCounts = useCounts - self._targetSize = targetSize - self._generate_fp_generator(targetSize=targetSize, includeChirality=includeChirality, - nBits=nBits) - - @property - def useCounts(self): - return self._useCounts - - @useCounts.setter - def useCounts(self, value:bool): - self._useCounts = value - - @property - def confId(self): - return self._confId - - @confId.setter - def confId(self, value: int): - self._confId = value - - @property - def fromAtoms(self): - return self._fromAtoms - - @fromAtoms.setter - def fromAtoms(self, value: int): - self._fromAtoms = value - - @property - def ignoreAtoms(self): - return self._ignoreAtoms - - @ignoreAtoms.setter - def ignoreAtoms(self, value: int): - self._ignoreAtoms = value - - @property - def atomInvariants(self): - return self._atomInvariants - - @atomInvariants.setter - def atomInvariants(self, value: int): - self._atomInvariants = value - - @property - def nBits(self): - return self._fpgen.GetOptions().fpSize - - @nBits.setter - def nBits(self, value: int): - self._fpgen.GetOptions().fpSize = value - - @property - def includeChirality(self): - return self._fpgen.GetOptions().includeChirality + self.fpSize = fpSize + self.includeChirality = includeChirality + self.targetSize = targetSize - @includeChirality.setter - def includeChirality(self, value:int): - self._fpgen.GetOptions().includeChirality = value + self.fromAtoms = fromAtoms + self.ignoreAtoms = ignoreAtoms + self.atomInvariants = atomInvariants + self.confId = confId + self.useCounts = useCounts - @property - def targetSize(self): - return self._targetSize + self._generate_fp_generator() + delattr(self, "_initializing") - @targetSize.setter - def targetSize(self, value:int): - self._targetSize = value - self._generate_fp_generator(targetSize=value, - includeChirality=self.includeChirality, - nBits=self.nBits) - def _generate_fp_generator(self, targetSize: int, includeChirality: bool, nBits: int): - self._fpgen = GetTopologicalTorsionGenerator(torsionAtomCount=targetSize, includeChirality=includeChirality, - fpSize=nBits) + def _generate_fp_generator(self): + self._fpgen = GetTopologicalTorsionGenerator(torsionAtomCount=self.targetSize, includeChirality=self.includeChirality, + fpSize=self.fpSize) def _transform_mol(self, mol) -> np.array: if self.useCounts: - return self._fpgen.GetCountFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self._ignoreAtoms, customAtomInvariants=self._atomInvariants) + return self._fpgen.GetCountFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self.ignoreAtoms, customAtomInvariants=self.atomInvariants) else: - return self._fpgen.GetFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self._ignoreAtoms, customAtomInvariants=self._atomInvariants) + return self._fpgen.GetFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self.ignoreAtoms, customAtomInvariants=self.atomInvariants) class AtomPairFPGeneratorTransformer(FpsGeneratorTransformer): + _regenerate_on_properties = ("fpSize", "includeChirality", "use2D", "minLength", "maxLength") + def __init__(self, minLength:int = 1, maxLength:int = 30, fromAtoms = None, ignoreAtoms = None, atomInvariants = None, - includeChirality:bool = False, use2D:bool = True, confId:int = -1, nBits=2048, + includeChirality:bool = False, use2D:bool = True, confId:int = -1, fpSize:int=2048, useCounts:bool=False, parallel: Union[bool, int] = False,): + self._initializing = True super().__init__(parallel = parallel) - self._useCounts= useCounts - self._confId = confId - self._fromAtoms = fromAtoms - self._ignoreAtoms = ignoreAtoms - self._atomInvariants = atomInvariants - self._minLength = minLength - self._maxLength = maxLength - - self._generate_fp_generator(minLength=minLength, maxLength=maxLength, - includeChirality=includeChirality, use2D=use2D, - nBits=nBits) - - @property - def useCounts(self): - return self._useCounts - - @useCounts.setter - def useCounts(self, value:bool): - self._useCounts = value - - @property - def confId(self): - return self._confId - - @confId.setter - def confId(self, value:int): - self._confId = value - - @property - def fromAtoms(self): - return self._fromAtoms - - @fromAtoms.setter - def fromAtoms(self, value:int): - self._fromAtoms = value - - @property - def ignoreAtoms(self): - return self._ignoreAtoms - - @ignoreAtoms.setter - def ignoreAtoms(self, value:int): - self._ignoreAtoms = value - - @property - def atomInvariants(self): - return self._atomInvariants - - @atomInvariants.setter - def atomInvariants(self, value:int): - self._atomInvariants = value - - @property - def minLength(self): - return self._minLength - - @minLength.setter - def minLength(self, value: int): - self._minLength = value - self._generate_fp_generator(minLength=value, maxLength=self.maxLength, - includeChirality=self.includeChirality, use2D=self.use2D, - nBits=self.nBits) - - @property - def maxLength(self): - return self._maxLength - - @maxLength.setter - def maxLength(self, value: int): - self._maxLength = value - self._generate_fp_generator(minLength=self.minLength, maxLength=value, - includeChirality=self.includeChirality, use2D=self.use2D, - nBits=self.nBits) - - @property - def includeChirality(self): - return self._fpgen.GetOptions().includeChirality - - @includeChirality.setter - def includeChirality(self, value: bool): - self._fpgen.GetOptions().includeChirality = value - - @property - def use2D(self): - return self._fpgen.GetOptions().use2D - - @use2D.setter - def use2D(self, value: bool): - self._fpgen.GetOptions().use2D = value - - @property - def nBits(self): - return self._fpgen.GetOptions().fpSize + self.fpSize = fpSize + self.use2D = use2D + self.includeChirality = includeChirality + self.minLength = minLength + self.maxLength = maxLength - @nBits.setter - def nBits(self, value: int): - self._fpgen.GetOptions().fpSize = value + self.useCounts= useCounts + self.confId = confId + self.fromAtoms = fromAtoms + self.ignoreAtoms = ignoreAtoms + self.atomInvariants = atomInvariants - @property - def nBitsPerEntry(self): - return self._fpgen.GetOptions().numBitsPerFeature + self._generate_fp_generator() + delattr(self, "_initializing") - def _generate_fp_generator(self, minLength, maxLength, includeChirality, use2D, nBits): - self._fpgen = GetAtomPairGenerator(minDistance=minLength, maxDistance=maxLength, - includeChirality=includeChirality, - use2D=use2D, fpSize=nBits) + def _generate_fp_generator(self): + self._fpgen = GetAtomPairGenerator(minDistance=self.minLength, maxDistance=self.maxLength, + includeChirality=self.includeChirality, + use2D=self.use2D, fpSize=self.fpSize) def _transform_mol(self, mol) -> np.array: if self.useCounts: - return self._fpgen.GetCountFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self._ignoreAtoms, customAtomInvariants=self._atomInvariants) + return self._fpgen.GetCountFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self.ignoreAtoms, customAtomInvariants=self.atomInvariants) else: - return self._fpgen.GetFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self._ignoreAtoms, customAtomInvariants=self._atomInvariants) + return self._fpgen.GetFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self.ignoreAtoms, customAtomInvariants=self.atomInvariants) class RDKitFPGeneratorTransformer(FpsGeneratorTransformer): + _regenerate_on_properties = ("minPath", "maxPath", "useHs", "branchedPaths", "useBondOrder", "countSimulation", "fpSize", "countBounds", + "numBitsPerFeature") + def __init__(self, minPath:int = 1, maxPath:int =7, useHs:bool = True, branchedPaths:bool = True, useBondOrder:bool = True, countSimulation:bool = False, countBounds = None, - nBits:int = 2048, numBitsPerFeature:int = 2, + fpSize:int = 2048, numBitsPerFeature:int = 2, useCounts:bool = False, parallel: Union[bool, int] = False ): """Calculates the RDKit fingerprints @@ -1125,88 +932,27 @@ def __init__(self, minPath:int = 1, maxPath:int =7, useHs:bool = True, branchedP if set, use count simulation while generating the fingerprint, by default False countBounds : _type_, optional boundaries for count simulation, corresponding bit will be set if the count is higher than the number provided for that spot, by default None - nBits : int, optional + fpSize : int, optional size of the generated fingerprint, does not affect the sparse versions, by default 2048 numBitsPerFeature : int, optional the number of bits set per path/subgraph found, by default 2 """ + self._initializing = True super().__init__(parallel = parallel) - self._useCounts = useCounts - self._countBounds = countBounds - self._generate_fp_generator( minPath=minPath, maxPath=maxPath, useHs=useHs, - branchedPaths=branchedPaths,useBondOrder=useBondOrder, - countSimulation=countSimulation, fpSize=nBits, - countBounds=countBounds, numBitsPerFeature=numBitsPerFeature) + self.minPath = minPath + self.maxPath = maxPath + self.useHs = useHs + self.branchedPaths = branchedPaths + self.useBondOrder = useBondOrder + self.countSimulation = countSimulation + self.fpSize = fpSize + self.numBitsPerFeature = numBitsPerFeature + self.countBounds = countBounds + self.useCounts = useCounts - @property - def nBits(self): - return self._fpgen.GetOptions().fpSize - @nBits.setter - def nBits(self, value: int): - self._fpgen.GetOptions().fpSize = value - @property - def minPath(self): - return self._fpgen.GetOptions().minPath - @minPath.setter - def minPath(self, value:int): - self._fpgen.GetOptions().minPath = value - @property - def maxPath(self): - return self._fpgen.GetOptions().maxPath - @maxPath.setter - def maxPath(self, value:int): - self._fpgen.GetOptions().maxPath = value - @property - def useHs(self): - return self._fpgen.GetOptions().useHs - @useHs.setter - def useHs(self, value:bool): - self._fpgen.GetOptions().useHs = value - @property - def branchedPaths(self): - return self._fpgen.GetOptions().branchedPaths - @branchedPaths.setter - def branchedPaths(self, value:int): - self._fpgen.GetOptions().branchedPaths = value - @property - def useBondOrder(self): - return self._fpgen.GetOptions().useBondOrder - @useBondOrder.setter - def useBondOrder(self, value:int): - self._fpgen.GetOptions().useBondOrder = value - @property - def numBitsPerFeature(self): - return self._fpgen.GetOptions().numBitsPerFeature - @numBitsPerFeature.setter - def numBitsPerFeature(self, value:int): - self._fpgen.GetOptions().numBitsPerFeature = value - @property - def countBounds(self): - return self._countBounds - @countBounds.setter - def countBounds(self, value:int): - self._countBounds = value - self._generate_fp_generator(minPath=self.minPath, maxPath=self.maxPath, useHs=self.useHs, - branchedPaths=self.branchedPaths,useBondOrder=self.useBondOrder, - countSimulation=self.countSimulation, fpSize=self.nBits, - countBounds=value, numBitsPerFeature=self.numBitsPerFeature) - @property - def countSimulation(self): - return self._countBounds - @countSimulation.setter - def countSimulation(self, value: bool): - self._countSimulation=value - self._generate_fp_generator(minPath=self.minPath, maxPath=self.maxPath, useHs=self.useHs, - branchedPaths=self.branchedPaths,useBondOrder=self.useBondOrder, - countSimulation=value, fpSize=self.nBits, - countBounds=self.countBounds, numBitsPerFeature=self.numBitsPerFeature) - @property - def useCounts(self): - return self._useCounts - @useCounts.setter - def useCounts(self, value:bool): - self._useCounts = value + self._generate_fp_generator() + delattr(self, "_initializing") def _transform_mol(self, mol) -> np.array: if self.useCounts: @@ -1214,10 +960,8 @@ def _transform_mol(self, mol) -> np.array: else: return self._fpgen.GetFingerprintAsNumPy(mol) - def _generate_fp_generator(self, minPath, maxPath, useHs, branchedPaths, - useBondOrder, countSimulation, fpSize, countBounds, - numBitsPerFeature): - self._fpgen = GetRDKitFPGenerator(minPath=minPath, maxPath=maxPath, useHs=useHs, - branchedPaths=branchedPaths,useBondOrder=useBondOrder, - countSimulation=countSimulation, fpSize=fpSize, - countBounds=countBounds, numBitsPerFeature=numBitsPerFeature) + def _generate_fp_generator(self): + self._fpgen = GetRDKitFPGenerator(minPath=self.minPath, maxPath=self.maxPath, useHs=self.useHs, + branchedPaths=self.branchedPaths,useBondOrder=self.useBondOrder, + countSimulation=self.countSimulation, fpSize=self.fpSize, + countBounds=self.countBounds, numBitsPerFeature=self.numBitsPerFeature) diff --git a/tests/test_fptransformers.py b/tests/test_fptransformers.py index 9a9c27a..4ad1e9d 100644 --- a/tests/test_fptransformers.py +++ b/tests/test_fptransformers.py @@ -131,34 +131,20 @@ def test_set_params( ]: params = t.get_params() # change extracted dictionary - params["nBits"] = 4242 + params["fpSize"] = 4242 # change params in transformer - t.set_params(nBits=4242) + t.set_params(fpSize=4242) # get parameters as dictionary and assert that it is the same params_2 = t.get_params() assert all([params[key] == params_2[key] for key in params.keys()]) - for t in [rdkit_transformer]: + for t in [rdkit_transformer, secfp_transformer, mhfp_transformer]: params = t.get_params() params["fpSize"] = 4242 t.set_params(fpSize=4242) params_2 = t.get_params() assert all([params[key] == params_2[key] for key in params.keys()]) - for t in [secfp_transformer]: - params = t.get_params() - params["length"] = 4242 - t.set_params(length=4242) - params_2 = t.get_params() - assert all([params[key] == params_2[key] for key in params.keys()]) - - for t in [mhfp_transformer]: - params = t.get_params() - params["n_permutations"] = 4242 - t.set_params(n_permutations=4242) - params_2 = t.get_params() - assert all([params[key] == params_2[key] for key in params.keys()]) - def test_transform( mols_container, @@ -183,21 +169,13 @@ def test_transform( avalon_transformer, ]: params = t.get_params() + print(type(t), params) fps = t.transform(mols_container) # Assert that the same length of input and output assert len(fps) == len(mols_container) # assert that the size of the fingerprint is the expected size - if ( - type(t) == type(maccs_transformer) - or type(t) == type(secfp_transformer) - or type(t) == type(mhfp_transformer) - ): - fpsize = t.nBits - elif type(t) == type(rdkit_transformer): - fpsize = params["fpSize"] - else: - fpsize = params["nBits"] + fpsize = params["fpSize"] assert len(fps[0]) == fpsize @@ -231,16 +209,7 @@ def test_transform_parallel( assert len(fps) == len(mols_container) # assert that the size of the fingerprint is the expected size - if ( - type(t) == type(maccs_transformer) - or type(t) == type(secfp_transformer) - or type(t) == type(mhfp_transformer) - ): - fpsize = t.nBits - elif type(t) == type(rdkit_transformer): - fpsize = params["fpSize"] - else: - fpsize = params["nBits"] + fpsize = params["fpSize"] assert len(fps[0]) == fpsize @@ -306,7 +275,7 @@ def assert_transformer_set_params(tr_class, new_params, mols_list): def test_morgan_set_params(chiral_mols_list): new_params = { - "nBits": 1024, + "fpSize": 1024, "radius": 1, "useBondTypes": False, # TODO, why doesn't this change the FP? "useChirality": True, @@ -328,7 +297,7 @@ def test_atompairs_set_params(chiral_mols_list): "includeChirality": True, "maxLength": 3, "minLength": 3, - "nBits": 1024, + "fpSize": 1024, "nBitsPerEntry": 3, #'use2D': True, #TODO, understand why this can't be set different "useCounts": True, @@ -344,7 +313,7 @@ def test_topologicaltorsion_set_params(chiral_mols_list): #'fromAtoms': 0, #'ignoreAtoms': 0, #'includeChirality': True, #TODO, figure out why this setting seems to give same FP wheter toggled or not - "nBits": 1024, + "fpSize": 1024, "nBitsPerEntry": 3, "targetSize": 5, "useCounts": True, @@ -376,7 +345,7 @@ def test_SECFingerprintTransformer(chiral_mols_list): new_params = { "isomeric": True, "kekulize": True, - "length": 1048, + "fpSize": 1048, "min_radius": 2, #'n_permutations': 2, # The SECFp is not using this setting "radius": 2, @@ -395,7 +364,7 @@ def test_MHFingerprintTransformer(chiral_mols_list): "isomeric": True, "kekulize": True, "min_radius": 2, - "n_permutations": 4096, + "fpSize": 4096, "seed": 44, } assert_transformer_set_params( @@ -405,7 +374,7 @@ def test_MHFingerprintTransformer(chiral_mols_list): def test_AvalonFingerprintTransformer(chiral_mols_list): new_params = { - "nBits": 1024, + "fpSize": 1024, "isQuery": True, # 'resetVect': True, #TODO: this doesn't change the FP "bitFlags": 32767, diff --git a/tests/test_fptransformersgenerator.py b/tests/test_fptransformersgenerator.py index f11ea95..81da19c 100644 --- a/tests/test_fptransformersgenerator.py +++ b/tests/test_fptransformersgenerator.py @@ -60,9 +60,9 @@ def test_set_params(transformer_class): transformer = transformer_class() params = transformer.get_params() #change extracted dictionary - params['nBits'] = 4242 + params['fpSize'] = 4242 #change params in transformer - transformer.set_params(nBits = 4242) + transformer.set_params(fpSize = 4242) # get parameters as dictionary and assert that it is the same params_2 = transformer.get_params() assert all([ params[key] == params_2[key] for key in params.keys()]) @@ -76,7 +76,7 @@ def test_transform(mols_container, transformer_class): #Assert that the same length of input and output assert len(fps) == len(mols_container) - fpsize = params['nBits'] + fpsize = params['fpSize'] assert len(fps[0]) == fpsize @@ -90,7 +90,7 @@ def test_transform_parallel(mols_container, transformer_class): #Assert that the same length of input and output assert len(fps) == len(mols_container) - fpsize = params['nBits'] + fpsize = params['fpSize'] assert len(fps[0]) == fpsize @@ -134,7 +134,7 @@ def assert_transformer_set_params(transfomer, new_params, mols_list): def test_morgan_set_params(chiral_mols_list): - new_params = {'nBits': 1024, + new_params = {'fpSize': 1024, 'radius': 1, 'useBondTypes': False,# TODO, why doesn't this change the FP? 'useChirality': True, @@ -153,7 +153,7 @@ def test_atompairs_set_params(chiral_mols_list): 'includeChirality': True, 'maxLength': 3, 'minLength': 3, - 'nBits': 1024, + 'fpSize': 1024, #'nBitsPerEntry': 3, #Todo: not setable with the generators? #'use2D': True, #TODO, understand why this can't be set different 'useCounts': True} @@ -166,7 +166,7 @@ def test_topologicaltorsion_set_params(chiral_mols_list): #'fromAtoms': 0, #'ignoreAtoms': 0, #'includeChirality': True, #TODO, figure out why this setting seems to give same FP wheter toggled or not - 'nBits': 1024, + 'fpSize': 1024, #'nBitsPerEntry': 3, #Todo: not setable with the generators? 'targetSize': 5, 'useCounts': True} @@ -178,7 +178,7 @@ def test_RDKitFPTransformer(chiral_mols_list): #'branchedPaths': False, #'countBounds': 0, #TODO: What does this do? 'countSimulation': True, - 'nBits': 1024, + 'fpSize': 1024, 'maxPath': 3, 'minPath': 2, 'numBitsPerFeature': 3, diff --git a/tests/test_safeinferencemode.py b/tests/test_safeinferencemode.py index 921cc0f..c9b4ca1 100644 --- a/tests/test_safeinferencemode.py +++ b/tests/test_safeinferencemode.py @@ -104,12 +104,12 @@ def test_safeinference_wrapper_pandas_output( result = smiles_pipeline[:-1].fit_transform(X_smiles) assert isinstance(result, pd.DataFrame) assert result.shape[0] == len(X_smiles) - assert result.shape[1] == smiles_pipeline.named_steps["FP"].nBits + assert result.shape[1] == smiles_pipeline.named_steps["FP"].fpSize @skip_pandas_output_test def test_safeinference_wrapper_get_feature_names_out(smiles_pipeline): # Get feature names from the FP step feature_names = smiles_pipeline.named_steps["FP"].get_feature_names_out() - assert len(feature_names) == smiles_pipeline.named_steps["FP"].nBits + assert len(feature_names) == smiles_pipeline.named_steps["FP"].fpSize assert all(isinstance(name, str) for name in feature_names) diff --git a/tests/test_transformers.py b/tests/test_transformers.py index 143ecd3..fa65504 100644 --- a/tests/test_transformers.py +++ b/tests/test_transformers.py @@ -96,11 +96,12 @@ def test_transformer_pandas_output(SLC6A4_subset, pandas_output): X_transformed = pipeline.transform(X_smiles) assert isinstance(X_transformed, pd.DataFrame), f"the output of {FP_name} is not a pandas dataframe" assert X_transformed.shape[0] == len(X_smiles), f"the number of rows in the output of {FP_name} is not equal to the number of samples" - assert len(X_transformed.columns) == pipeline.named_steps["FP"].nBits, f"the number of columns in the output of {FP_name} is not equal to the number of bits" + assert len(X_transformed.columns) == pipeline.named_steps["FP"].fpSize, f"the number of columns in the output of {FP_name} is not equal to the number of bits" print(f"\nfitting and transforming completed") - except: + except Exception as err: print(f"\n!!!! FAILED pipeline fitting and transforming for {FP_name} with useCounts={useCounts}") + print("\n".join(err.args)) failed_FP.append(FP_name) pass @@ -136,7 +137,7 @@ def test_combined_transformer_pandas_out(combined_transformer, SLC6A4_subset_wit pipeline_skmol = combined_transformer.named_transformers_["pipeline-1"] featurizer_skmol = pipeline_skmol[-1] if isinstance(featurizer_skmol, FpsTransformer): - n_skmol_features = featurizer_skmol.nBits + n_skmol_features = featurizer_skmol.fpSize elif isinstance(featurizer_skmol, MolecularDescriptorTransformer): n_skmol_features = len(featurizer_skmol.desc_list) else: From d420cbde3ec5222c4ba36a4e3511b567101310a6 Mon Sep 17 00:00:00 2001 From: riesben Date: Thu, 14 Nov 2024 22:48:15 +0100 Subject: [PATCH 07/24] Remodelling transformers: - moving code around for easier oversight - adding nicer dpecrecation warnings. --- scikit_mol/fingerprints.py | 450 ++++++++++++++++++------------------- 1 file changed, 223 insertions(+), 227 deletions(-) diff --git a/scikit_mol/fingerprints.py b/scikit_mol/fingerprints.py index a96e65b..a6f90bc 100644 --- a/scikit_mol/fingerprints.py +++ b/scikit_mol/fingerprints.py @@ -2,9 +2,8 @@ import multiprocessing import re import inspect -from typing import Callable +from warnings import warn from typing import Union -from rdkit import Chem from rdkit import DataStructs # from rdkit.Chem.AllChem import GetMorganFingerprintAsBitVect @@ -33,7 +32,6 @@ r"^(?P\w+)FingerprintTransformer$" ) - class FpsTransformer(ABC, BaseEstimator, TransformerMixin): def __init__( self, @@ -220,200 +218,6 @@ def _mol2fp(self, mol): return rdMolDescriptors.GetMACCSKeysFingerprint(mol) -class RDKitFingerprintTransformer(FpsTransformer): - def __init__( - self, - minPath: int = 1, - maxPath: int = 7, - useHs: bool = True, - branchedPaths: bool = True, - useBondOrder: bool = True, - countSimulation: bool = False, - countBounds=None, - fpSize: int = 2048, - numBitsPerFeature: int = 2, - atomInvariantsGenerator=None, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = np.int8, - ): - """Calculates the RDKit fingerprints - - Parameters - ---------- - minPath : int, optional - the minimum path length (in bonds) to be included, by default 1 - maxPath : int, optional - the maximum path length (in bonds) to be included, by default 7 - useHs : bool, optional - toggles inclusion of Hs in paths (if the molecule has explicit Hs), by default True - branchedPaths : bool, optional - toggles generation of branched subgraphs, not just linear paths, by default True - useBondOrder : bool, optional - toggles inclusion of bond orders in the path hashes, by default True - countSimulation : bool, optional - if set, use count simulation while generating the fingerprint, by default False - countBounds : _type_, optional - boundaries for count simulation, corresponding bit will be set if the count is higher than the number provided for that spot, by default None - fpSize : int, optional - size of the generated fingerprint, does not affect the sparse versions, by default 2048 - numBitsPerFeature : int, optional - the number of bits set per path/subgraph found, by default 2 - atomInvariantsGenerator : _type_, optional - atom invariants to be used during fingerprint generation, by default None - """ - super().__init__( - parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype - ) - self.minPath = minPath - self.maxPath = maxPath - self.useHs = useHs - self.branchedPaths = branchedPaths - self.useBondOrder = useBondOrder - self.countSimulation = countSimulation - self.countBounds = countBounds - self.fpSize = fpSize - self.numBitsPerFeature = numBitsPerFeature - self.atomInvariantsGenerator = atomInvariantsGenerator - - - def _mol2fp(self, mol): - generator = rdFingerprintGenerator.GetRDKitFPGenerator( - minPath=int(self.minPath), - maxPath=int(self.maxPath), - useHs=bool(self.useHs), - branchedPaths=bool(self.branchedPaths), - useBondOrder=bool(self.useBondOrder), - countSimulation=bool(self.countSimulation), - countBounds=bool(self.countBounds), - fpSize=int(self.fpSize), - numBitsPerFeature=int(self.numBitsPerFeature), - atomInvariantsGenerator=self.atomInvariantsGenerator, - ) - return generator.GetFingerprint(mol) - - -class AtomPairFingerprintTransformer(FpsTransformer): - def __init__( - self, - minLength: int = 1, - maxLength: int = 30, - fromAtoms=0, - ignoreAtoms=0, - atomInvariants=0, - nBitsPerEntry: int = 4, - includeChirality: bool = False, - use2D: bool = True, - confId: int = -1, - fpSize=2048, - useCounts: bool = False, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = np.int8, - ): - super().__init__( - parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype - ) - self.minLength = minLength - self.maxLength = maxLength - self.fromAtoms = fromAtoms - self.ignoreAtoms = ignoreAtoms - self.atomInvariants = atomInvariants - self.includeChirality = includeChirality - self.use2D = use2D - self.confId = confId - self.fpSize = fpSize - self.nBitsPerEntry = nBitsPerEntry - self.useCounts = useCounts - - print("AtomPairFingerprintTransformer will be replace by AtomPairFPGeneratorTransformer, due to changes in RDKit!") - #raise DeprecationWarning("AtomPairFingerprintTransformer will be replace by AtomPairFPGeneratorTransformer, due to changes in RDKit!") - - - def _mol2fp(self, mol): - if self.useCounts: - return rdMolDescriptors.GetHashedAtomPairFingerprint( - mol, - nBits=int(self.fpSize), - minLength=int(self.minLength), - maxLength=int(self.maxLength), - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - atomInvariants=self.atomInvariants, - includeChirality=bool(self.includeChirality), - use2D=bool(self.use2D), - confId=int(self.confId), - ) - else: - return rdMolDescriptors.GetHashedAtomPairFingerprintAsBitVect( - mol, - nBits=int(self.fpSize), - minLength=int(self.minLength), - maxLength=int(self.maxLength), - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - atomInvariants=self.atomInvariants, - nBitsPerEntry=int(self.nBitsPerEntry), - includeChirality=bool(self.includeChirality), - use2D=bool(self.use2D), - confId=int(self.confId), - ) - - -class TopologicalTorsionFingerprintTransformer(FpsTransformer): - def __init__( - self, - targetSize: int = 4, - fromAtoms=0, - ignoreAtoms=0, - atomInvariants=0, - includeChirality: bool = False, - nBitsPerEntry: int = 4, - fpSize=2048, - useCounts: bool = False, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = np.int8, - ): - super().__init__( - parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype - ) - self.targetSize = targetSize - self.fromAtoms = fromAtoms - self.ignoreAtoms = ignoreAtoms - self.atomInvariants = atomInvariants - self.includeChirality = includeChirality - self.nBitsPerEntry = nBitsPerEntry - self.fpSize = fpSize - self.useCounts = useCounts - print("TopologicalTorsionFingerprintTransformer will be replace by TopologicalTorsionFPGeneatorTransformer, due to changes in RDKit!") - #raise DeprecationWarning("AtomPairFingerprintTransformer will be replace by AtomPairFPGeneratorTransformer, due to changes in RDKit!") - - - def _mol2fp(self, mol): - if self.useCounts: - return rdMolDescriptors.GetHashedTopologicalTorsionFingerprint( - mol, - nBits=int(self.fpSize), - targetSize=int(self.targetSize), - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - atomInvariants=self.atomInvariants, - includeChirality=bool(self.includeChirality), - ) - else: - return rdMolDescriptors.GetHashedTopologicalTorsionFingerprintAsBitVect( - mol, - nBits=int(self.fpSize), - targetSize=int(self.targetSize), - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - atomInvariants=self.atomInvariants, - includeChirality=bool(self.includeChirality), - nBitsPerEntry=int(self.nBitsPerEntry), - ) - - class MHFingerprintTransformer(FpsTransformer): def __init__( self, @@ -602,6 +406,61 @@ def length(self): return self.fpSize +class AvalonFingerprintTransformer(FpsTransformer): + # Fingerprint from the Avalon toolkeit, https://doi.org/10.1021/ci050413p + def __init__( + self, + fpSize: int = 512, + isQuery: bool = False, + resetVect: bool = False, + bitFlags: int = 15761407, + useCounts: bool = False, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + dtype: np.dtype = np.int8, + ): + """Transform RDKit mols into Count or bit-based Avalon Fingerprints + + Parameters + ---------- + fpSize : int, optional + Size of the fingerprint, by default 512 + isQuery : bool, optional + use the fingerprint for a query structure, by default False + resetVect : bool, optional + reset vector, by default False NB: only used in GetAvalonFP (not for GetAvalonCountFP) + bitFlags : int, optional + Substructure fingerprint (32767) or similarity fingerprint (15761407) by default 15761407 + useCounts : bool, optional + If toggled will create the count and not bit-based fingerprint, by default False + """ + super().__init__( + parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype + ) + self.fpSize = fpSize + self.isQuery = isQuery + self.resetVect = resetVect + self.bitFlags = bitFlags + self.useCounts = useCounts + + def _mol2fp(self, mol): + if self.useCounts: + return pyAvalonTools.GetAvalonCountFP( + mol, + nBits=int(self.fpSize), + isQuery=bool(self.isQuery), + bitFlags=int(self.bitFlags), + ) + else: + return pyAvalonTools.GetAvalonFP( + mol, + nBits=int(self.fpSize), + isQuery=bool(self.isQuery), + resetVect=bool(self.resetVect), + bitFlags=int(self.bitFlags), + ) + + class MorganFingerprintTransformer(FpsTransformer): def __init__( self, @@ -642,9 +501,7 @@ def __init__( self.useFeatures = useFeatures self.useCounts = useCounts - print("MorganFingerprintTransformer will be replace by MorganGeneratorTransformer, due to changes in RDKit!") - #raise DeprecationWarning("MorganFingerprintTransformer will be replace by MorganFPGeneratorTransformer, due to changes in RDKit!") - + warn("MorganFingerprintTransformer will be replace by MorganGeneratorTransformer, due to changes in RDKit!", DeprecationWarning) def _mol2fp(self, mol): if self.useCounts: @@ -667,58 +524,196 @@ def _mol2fp(self, mol): ) -class AvalonFingerprintTransformer(FpsTransformer): - # Fingerprint from the Avalon toolkeit, https://doi.org/10.1021/ci050413p +class RDKitFingerprintTransformer(FpsTransformer): def __init__( self, - fpSize: int = 512, - isQuery: bool = False, - resetVect: bool = False, - bitFlags: int = 15761407, - useCounts: bool = False, + minPath: int = 1, + maxPath: int = 7, + useHs: bool = True, + branchedPaths: bool = True, + useBondOrder: bool = True, + countSimulation: bool = False, + countBounds=None, + fpSize: int = 2048, + numBitsPerFeature: int = 2, + atomInvariantsGenerator=None, parallel: Union[bool, int] = False, safe_inference_mode: bool = False, dtype: np.dtype = np.int8, ): - """Transform RDKit mols into Count or bit-based Avalon Fingerprints + """Calculates the RDKit fingerprints Parameters ---------- + minPath : int, optional + the minimum path length (in bonds) to be included, by default 1 + maxPath : int, optional + the maximum path length (in bonds) to be included, by default 7 + useHs : bool, optional + toggles inclusion of Hs in paths (if the molecule has explicit Hs), by default True + branchedPaths : bool, optional + toggles generation of branched subgraphs, not just linear paths, by default True + useBondOrder : bool, optional + toggles inclusion of bond orders in the path hashes, by default True + countSimulation : bool, optional + if set, use count simulation while generating the fingerprint, by default False + countBounds : _type_, optional + boundaries for count simulation, corresponding bit will be set if the count is higher than the number provided for that spot, by default None fpSize : int, optional - Size of the fingerprint, by default 512 - isQuery : bool, optional - use the fingerprint for a query structure, by default False - resetVect : bool, optional - reset vector, by default False NB: only used in GetAvalonFP (not for GetAvalonCountFP) - bitFlags : int, optional - Substructure fingerprint (32767) or similarity fingerprint (15761407) by default 15761407 - useCounts : bool, optional - If toggled will create the count and not bit-based fingerprint, by default False + size of the generated fingerprint, does not affect the sparse versions, by default 2048 + numBitsPerFeature : int, optional + the number of bits set per path/subgraph found, by default 2 + atomInvariantsGenerator : _type_, optional + atom invariants to be used during fingerprint generation, by default None """ super().__init__( parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype ) + self.minPath = minPath + self.maxPath = maxPath + self.useHs = useHs + self.branchedPaths = branchedPaths + self.useBondOrder = useBondOrder + self.countSimulation = countSimulation + self.countBounds = countBounds self.fpSize = fpSize - self.isQuery = isQuery - self.resetVect = resetVect - self.bitFlags = bitFlags + self.numBitsPerFeature = numBitsPerFeature + self.atomInvariantsGenerator = atomInvariantsGenerator + + warn("RDKitFingerprintTransformer will be replace by RDKitFPGeneratorTransformer, due to changes in RDKit!", DeprecationWarning) + + + def _mol2fp(self, mol): + generator = rdFingerprintGenerator.GetRDKitFPGenerator( + minPath=int(self.minPath), + maxPath=int(self.maxPath), + useHs=bool(self.useHs), + branchedPaths=bool(self.branchedPaths), + useBondOrder=bool(self.useBondOrder), + countSimulation=bool(self.countSimulation), + countBounds=bool(self.countBounds), + fpSize=int(self.fpSize), + numBitsPerFeature=int(self.numBitsPerFeature), + atomInvariantsGenerator=self.atomInvariantsGenerator, + ) + return generator.GetFingerprint(mol) + + +class AtomPairFingerprintTransformer(FpsTransformer): + def __init__( + self, + minLength: int = 1, + maxLength: int = 30, + fromAtoms=0, + ignoreAtoms=0, + atomInvariants=0, + nBitsPerEntry: int = 4, + includeChirality: bool = False, + use2D: bool = True, + confId: int = -1, + fpSize=2048, + useCounts: bool = False, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + dtype: np.dtype = np.int8, + ): + super().__init__( + parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype + ) + self.minLength = minLength + self.maxLength = maxLength + self.fromAtoms = fromAtoms + self.ignoreAtoms = ignoreAtoms + self.atomInvariants = atomInvariants + self.includeChirality = includeChirality + self.use2D = use2D + self.confId = confId + self.fpSize = fpSize + self.nBitsPerEntry = nBitsPerEntry self.useCounts = useCounts + warn("AtomPairFingerprintTransformer will be replace by AtomPairFPGeneratorTransformer, due to changes in RDKit!", DeprecationWarning) + def _mol2fp(self, mol): if self.useCounts: - return pyAvalonTools.GetAvalonCountFP( + return rdMolDescriptors.GetHashedAtomPairFingerprint( mol, nBits=int(self.fpSize), - isQuery=bool(self.isQuery), - bitFlags=int(self.bitFlags), + minLength=int(self.minLength), + maxLength=int(self.maxLength), + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + atomInvariants=self.atomInvariants, + includeChirality=bool(self.includeChirality), + use2D=bool(self.use2D), + confId=int(self.confId), ) else: - return pyAvalonTools.GetAvalonFP( + return rdMolDescriptors.GetHashedAtomPairFingerprintAsBitVect( mol, nBits=int(self.fpSize), - isQuery=bool(self.isQuery), - resetVect=bool(self.resetVect), - bitFlags=int(self.bitFlags), + minLength=int(self.minLength), + maxLength=int(self.maxLength), + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + atomInvariants=self.atomInvariants, + nBitsPerEntry=int(self.nBitsPerEntry), + includeChirality=bool(self.includeChirality), + use2D=bool(self.use2D), + confId=int(self.confId), + ) + + +class TopologicalTorsionFingerprintTransformer(FpsTransformer): + def __init__( + self, + targetSize: int = 4, + fromAtoms=0, + ignoreAtoms=0, + atomInvariants=0, + includeChirality: bool = False, + nBitsPerEntry: int = 4, + fpSize=2048, + useCounts: bool = False, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + dtype: np.dtype = np.int8, + ): + super().__init__( + parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype + ) + self.targetSize = targetSize + self.fromAtoms = fromAtoms + self.ignoreAtoms = ignoreAtoms + self.atomInvariants = atomInvariants + self.includeChirality = includeChirality + self.nBitsPerEntry = nBitsPerEntry + self.fpSize = fpSize + self.useCounts = useCounts + + warn("TopologicalTorsionFingerprintTransformer will be replace by TopologicalTorsionFPGeneatorTransformer, due to changes in RDKit!", DeprecationWarning) + + def _mol2fp(self, mol): + if self.useCounts: + return rdMolDescriptors.GetHashedTopologicalTorsionFingerprint( + mol, + nBits=int(self.fpSize), + targetSize=int(self.targetSize), + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + atomInvariants=self.atomInvariants, + includeChirality=bool(self.includeChirality), + ) + else: + return rdMolDescriptors.GetHashedTopologicalTorsionFingerprintAsBitVect( + mol, + nBits=int(self.fpSize), + targetSize=int(self.targetSize), + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + atomInvariants=self.atomInvariants, + includeChirality=bool(self.includeChirality), + nBitsPerEntry=int(self.nBitsPerEntry), ) @@ -732,6 +727,7 @@ def parallel_helper(args): transformer = getattr(fingerprints, classname)(**parameters) return transformer._transform(X_mols) + class FpsGeneratorTransformer(FpsTransformer): _regenerate_on_properties = () From f7d2958b91f24930288f3c5a68c7bdb23a80a633 Mon Sep 17 00:00:00 2001 From: riesben Date: Fri, 15 Nov 2024 07:03:12 +0100 Subject: [PATCH 08/24] Remodelling transformers: - add new generator functions to transformer test --- tests/test_transformers.py | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/tests/test_transformers.py b/tests/test_transformers.py index fa65504..b96d421 100644 --- a/tests/test_transformers.py +++ b/tests/test_transformers.py @@ -15,9 +15,11 @@ from sklearn.ensemble import RandomForestRegressor from scikit_mol.conversions import SmilesToMolTransformer from scikit_mol.core import SKLEARN_VERSION_PANDAS_OUT -from scikit_mol.fingerprints import FpsTransformer, MACCSKeysFingerprintTransformer, RDKitFingerprintTransformer, AtomPairFingerprintTransformer, \ - TopologicalTorsionFingerprintTransformer, MorganFingerprintTransformer, SECFingerprintTransformer, \ - MHFingerprintTransformer, AvalonFingerprintTransformer +from scikit_mol.fingerprints import (FpsTransformer, MACCSKeysFingerprintTransformer, RDKitFingerprintTransformer, AtomPairFingerprintTransformer, + TopologicalTorsionFingerprintTransformer, MorganFingerprintTransformer, SECFingerprintTransformer, + MHFingerprintTransformer, AvalonFingerprintTransformer, MorganFPGeneratorTransformer, + RDKitFPGeneratorTransformer, AtomPairFPGeneratorTransformer, TopologicalTorsionFPGeneatorTransformer) + from scikit_mol.descriptors import MolecularDescriptorTransformer from fixtures import SLC6A4_subset, SLC6A4_subset_with_cddd, skip_pandas_output_test, mols_container, featurizer, combined_transformer @@ -29,6 +31,9 @@ def test_transformer(SLC6A4_subset): X_train, X_test = X_smiles[:128], X_smiles[128:] Y_train, Y_test = Y[:128], Y[128:] + MorganFPGeneratorTransformer, + RDKitFPGeneratorTransformer, AtomPairFPGeneratorTransformer, TopologicalTorsionFPGeneatorTransformer + # run FP with default parameters except when useCounts can be given as an argument FP_dict = {"MACCSTransformer": [MACCSKeysFingerprintTransformer, None], "RDKitFPTransformer": [RDKitFingerprintTransformer, None], @@ -40,7 +45,15 @@ def test_transformer(SLC6A4_subset): "MorganTransformer useCounts": [MorganFingerprintTransformer, True], "SECFingerprintTransformer": [SECFingerprintTransformer, None], "MHFingerprintTransformer": [MHFingerprintTransformer, None], - 'AvalonFingerprintTransformer': [AvalonFingerprintTransformer, None]} + 'AvalonFingerprintTransformer': [AvalonFingerprintTransformer, None], + 'MorganFPGeneratorTransformer': [MorganFPGeneratorTransformer, True], + 'MorganFPGeneratorTransformer': [MorganFPGeneratorTransformer, False], + 'RDKitFPGeneratorTransformer': [RDKitFPGeneratorTransformer, None], + 'AtomPairFPGeneratorTransformer': [AtomPairFPGeneratorTransformer, True], + 'AtomPairFPGeneratorTransformer': [ AtomPairFPGeneratorTransformer, False], + 'TopologicalTorsionFPGeneatorTransformer': [TopologicalTorsionFPGeneatorTransformer, True], + 'TopologicalTorsionFPGeneatorTransformer': [ TopologicalTorsionFPGeneatorTransformer, False], + } # fit on toy data and print train/test score if successful or collect the failed FP failed_FP = [] @@ -81,7 +94,22 @@ def test_transformer_pandas_output(SLC6A4_subset, pandas_output): "MorganTransformer useCounts": [MorganFingerprintTransformer, True], "SECFingerprintTransformer": [SECFingerprintTransformer, None], "MHFingerprintTransformer": [MHFingerprintTransformer, None], - 'AvalonFingerprintTransformer': [AvalonFingerprintTransformer, None]} + 'AvalonFingerprintTransformer': [AvalonFingerprintTransformer, None], + 'MorganFPGeneratorTransformer': [MorganFPGeneratorTransformer, + True], + 'MorganFPGeneratorTransformer': [MorganFPGeneratorTransformer, + False], + 'RDKitFPGeneratorTransformer': [RDKitFPGeneratorTransformer, + None], + 'AtomPairFPGeneratorTransformer': [ + AtomPairFPGeneratorTransformer, True], + 'AtomPairFPGeneratorTransformer': [ + AtomPairFPGeneratorTransformer, False], + 'TopologicalTorsionFPGeneatorTransformer': [ + TopologicalTorsionFPGeneatorTransformer, True], + 'TopologicalTorsionFPGeneatorTransformer': [ + TopologicalTorsionFPGeneatorTransformer, False], + } # fit on toy data and check that the output is a pandas dataframe failed_FP = [] From 5ae6a2b326f9a47fcd6174be0b84cae9eeaccab4 Mon Sep 17 00:00:00 2001 From: riesben Date: Fri, 15 Nov 2024 07:16:15 +0100 Subject: [PATCH 09/24] Remodelling transformers: - add DeprecationWarnings to not harmonized fpSize bits. --- scikit_mol/fingerprints.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/scikit_mol/fingerprints.py b/scikit_mol/fingerprints.py index a6f90bc..bea43e0 100644 --- a/scikit_mol/fingerprints.py +++ b/scikit_mol/fingerprints.py @@ -45,6 +45,17 @@ def __init__( self.safe_inference_mode = safe_inference_mode self.dtype = dtype + + @property + def nBits(self): + warn("nBits will be replace by fpSize, due to changes harmonization!", DeprecationWarning) + return self.fpSize + + @nBits.setter + def nBits(self, nBits): + warn("nBits will be replace by fpSize, due to changes harmonization!", DeprecationWarning) + self.fpSize = nBits + def _get_column_prefix(self) -> str: matched = _PATTERN_FINGERPRINT_TRANSFORMER.match(type(self).__name__) if matched: @@ -299,10 +310,12 @@ def seed(self, seed): @property def n_permutations(self): + warn("n_permutations will be replace by fpSize, due to changes harmonization!", DeprecationWarning) return self.fpSize @n_permutations.setter def n_permutations(self, n_permutations): + warn("n_permutations will be replace by fpSize, due to changes harmonization!", DeprecationWarning) self.fpSize = n_permutations # each time the n_permutations parameter is modified refresh an instance of the encoder self._recreate_encoder() @@ -402,7 +415,7 @@ def n_permutations(self, n_permutations): @property def length(self): - # to be compliant with the requirement of the base class + warn("length will be replace by fpSize, due to changes harmonization!", DeprecationWarning) return self.fpSize From 5f91e0cf5edd9d4e27559399e4c5d1de6985e235 Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Fri, 22 Nov 2024 14:42:11 +0100 Subject: [PATCH 10/24] Preparing file split --- scikit_mol/{fingerprints.py => fingerprints/baseclasses.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scikit_mol/{fingerprints.py => fingerprints/baseclasses.py} (100%) diff --git a/scikit_mol/fingerprints.py b/scikit_mol/fingerprints/baseclasses.py similarity index 100% rename from scikit_mol/fingerprints.py rename to scikit_mol/fingerprints/baseclasses.py From f7b20f173136e95f1c363b85fdb01eb3afafd26d Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Fri, 22 Nov 2024 14:44:44 +0100 Subject: [PATCH 11/24] Split fingerprint file into smaller for better overview --- scikit_mol/fingerprints/__init__.py | 15 + scikit_mol/fingerprints/atompair.py | 144 ++++ scikit_mol/fingerprints/avalon.py | 62 ++ scikit_mol/fingerprints/baseclasses.py | 784 +----------------- scikit_mol/fingerprints/maccs.py | 41 + scikit_mol/fingerprints/minhash.py | 206 +++++ scikit_mol/fingerprints/morgan.py | 150 ++++ scikit_mol/fingerprints/rdkitfp.py | 175 ++++ scikit_mol/fingerprints/topologicaltorsion.py | 120 +++ 9 files changed, 950 insertions(+), 747 deletions(-) create mode 100644 scikit_mol/fingerprints/__init__.py create mode 100644 scikit_mol/fingerprints/atompair.py create mode 100644 scikit_mol/fingerprints/avalon.py create mode 100644 scikit_mol/fingerprints/maccs.py create mode 100644 scikit_mol/fingerprints/minhash.py create mode 100644 scikit_mol/fingerprints/morgan.py create mode 100644 scikit_mol/fingerprints/rdkitfp.py create mode 100644 scikit_mol/fingerprints/topologicaltorsion.py diff --git a/scikit_mol/fingerprints/__init__.py b/scikit_mol/fingerprints/__init__.py new file mode 100644 index 0000000..5ed655d --- /dev/null +++ b/scikit_mol/fingerprints/__init__.py @@ -0,0 +1,15 @@ +from .baseclasses import ( + FpsTransformer, + FpsGeneratorTransformer, +) # TODO, for backwards compatibility with tests, needs to be removed + +from .atompair import AtomPairFingerprintTransformer, AtomPairFPGeneratorTransformer +from .avalon import AvalonFingerprintTransformer +from .maccs import MACCSKeysFingerprintTransformer +from .minhash import MHFingerprintTransformer, SECFingerprintTransformer +from .morgan import MorganFingerprintTransformer, MorganFPGeneratorTransformer +from .rdkitfp import RDKitFingerprintTransformer, RDKitFPGeneratorTransformer +from .topologicaltorsion import ( + TopologicalTorsionFingerprintTransformer, + TopologicalTorsionFPGeneatorTransformer, +) diff --git a/scikit_mol/fingerprints/atompair.py b/scikit_mol/fingerprints/atompair.py new file mode 100644 index 0000000..aff8f9f --- /dev/null +++ b/scikit_mol/fingerprints/atompair.py @@ -0,0 +1,144 @@ +from typing import Union + +import numpy as np + +from warnings import warn + +from .baseclasses import FpsTransformer, FpsGeneratorTransformer + +from rdkit.Chem.rdFingerprintGenerator import GetAtomPairGenerator +from rdkit.Chem import rdMolDescriptors + + +class AtomPairFingerprintTransformer(FpsTransformer): + def __init__( + self, + minLength: int = 1, + maxLength: int = 30, + fromAtoms=0, + ignoreAtoms=0, + atomInvariants=0, + nBitsPerEntry: int = 4, + includeChirality: bool = False, + use2D: bool = True, + confId: int = -1, + fpSize=2048, + useCounts: bool = False, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + dtype: np.dtype = np.int8, + ): + super().__init__( + parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype + ) + self.minLength = minLength + self.maxLength = maxLength + self.fromAtoms = fromAtoms + self.ignoreAtoms = ignoreAtoms + self.atomInvariants = atomInvariants + self.includeChirality = includeChirality + self.use2D = use2D + self.confId = confId + self.fpSize = fpSize + self.nBitsPerEntry = nBitsPerEntry + self.useCounts = useCounts + + warn( + "AtomPairFingerprintTransformer will be replace by AtomPairFPGeneratorTransformer, due to changes in RDKit!", + DeprecationWarning, + ) + + def _mol2fp(self, mol): + if self.useCounts: + return rdMolDescriptors.GetHashedAtomPairFingerprint( + mol, + nBits=int(self.fpSize), + minLength=int(self.minLength), + maxLength=int(self.maxLength), + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + atomInvariants=self.atomInvariants, + includeChirality=bool(self.includeChirality), + use2D=bool(self.use2D), + confId=int(self.confId), + ) + else: + return rdMolDescriptors.GetHashedAtomPairFingerprintAsBitVect( + mol, + nBits=int(self.fpSize), + minLength=int(self.minLength), + maxLength=int(self.maxLength), + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + atomInvariants=self.atomInvariants, + nBitsPerEntry=int(self.nBitsPerEntry), + includeChirality=bool(self.includeChirality), + use2D=bool(self.use2D), + confId=int(self.confId), + ) + + +class AtomPairFPGeneratorTransformer(FpsGeneratorTransformer): + _regenerate_on_properties = ( + "fpSize", + "includeChirality", + "use2D", + "minLength", + "maxLength", + ) + + def __init__( + self, + minLength: int = 1, + maxLength: int = 30, + fromAtoms=None, + ignoreAtoms=None, + atomInvariants=None, + includeChirality: bool = False, + use2D: bool = True, + confId: int = -1, + fpSize: int = 2048, + useCounts: bool = False, + parallel: Union[bool, int] = False, + ): + self._initializing = True + super().__init__(parallel=parallel) + self.fpSize = fpSize + self.use2D = use2D + self.includeChirality = includeChirality + self.minLength = minLength + self.maxLength = maxLength + + self.useCounts = useCounts + self.confId = confId + self.fromAtoms = fromAtoms + self.ignoreAtoms = ignoreAtoms + self.atomInvariants = atomInvariants + + self._generate_fp_generator() + delattr(self, "_initializing") + + def _generate_fp_generator(self): + self._fpgen = GetAtomPairGenerator( + minDistance=self.minLength, + maxDistance=self.maxLength, + includeChirality=self.includeChirality, + use2D=self.use2D, + fpSize=self.fpSize, + ) + + def _transform_mol(self, mol) -> np.array: + if self.useCounts: + return self._fpgen.GetCountFingerprintAsNumPy( + mol, + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + customAtomInvariants=self.atomInvariants, + ) + else: + return self._fpgen.GetFingerprintAsNumPy( + mol, + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + customAtomInvariants=self.atomInvariants, + ) diff --git a/scikit_mol/fingerprints/avalon.py b/scikit_mol/fingerprints/avalon.py new file mode 100644 index 0000000..074632d --- /dev/null +++ b/scikit_mol/fingerprints/avalon.py @@ -0,0 +1,62 @@ +from typing import Union + +import numpy as np + +from .baseclasses import FpsTransformer + +from rdkit.Avalon import pyAvalonTools + + +class AvalonFingerprintTransformer(FpsTransformer): + # Fingerprint from the Avalon toolkeit, https://doi.org/10.1021/ci050413p + def __init__( + self, + fpSize: int = 512, + isQuery: bool = False, + resetVect: bool = False, + bitFlags: int = 15761407, + useCounts: bool = False, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + dtype: np.dtype = np.int8, + ): + """Transform RDKit mols into Count or bit-based Avalon Fingerprints + + Parameters + ---------- + fpSize : int, optional + Size of the fingerprint, by default 512 + isQuery : bool, optional + use the fingerprint for a query structure, by default False + resetVect : bool, optional + reset vector, by default False NB: only used in GetAvalonFP (not for GetAvalonCountFP) + bitFlags : int, optional + Substructure fingerprint (32767) or similarity fingerprint (15761407) by default 15761407 + useCounts : bool, optional + If toggled will create the count and not bit-based fingerprint, by default False + """ + super().__init__( + parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype + ) + self.fpSize = fpSize + self.isQuery = isQuery + self.resetVect = resetVect + self.bitFlags = bitFlags + self.useCounts = useCounts + + def _mol2fp(self, mol): + if self.useCounts: + return pyAvalonTools.GetAvalonCountFP( + mol, + nBits=int(self.fpSize), + isQuery=bool(self.isQuery), + bitFlags=int(self.bitFlags), + ) + else: + return pyAvalonTools.GetAvalonFP( + mol, + nBits=int(self.fpSize), + isQuery=bool(self.isQuery), + resetVect=bool(self.resetVect), + bitFlags=int(self.bitFlags), + ) diff --git a/scikit_mol/fingerprints/baseclasses.py b/scikit_mol/fingerprints/baseclasses.py index bea43e0..ce28e18 100644 --- a/scikit_mol/fingerprints/baseclasses.py +++ b/scikit_mol/fingerprints/baseclasses.py @@ -10,12 +10,15 @@ from rdkit.Chem import rdMolDescriptors from rdkit.Chem import rdFingerprintGenerator from rdkit.Chem import rdMHFPFingerprint -from rdkit.Avalon import pyAvalonTools -from rdkit.Chem.rdFingerprintGenerator import (GetMorganGenerator, GetMorganFeatureAtomInvGen, - GetTopologicalTorsionGenerator, - GetAtomPairGenerator, - GetRDKitFPGenerator) + +from rdkit.Chem.rdFingerprintGenerator import ( + GetMorganGenerator, + GetMorganFeatureAtomInvGen, + GetTopologicalTorsionGenerator, + GetAtomPairGenerator, + GetRDKitFPGenerator, +) import numpy as np import pandas as pd @@ -32,6 +35,7 @@ r"^(?P\w+)FingerprintTransformer$" ) + class FpsTransformer(ABC, BaseEstimator, TransformerMixin): def __init__( self, @@ -45,15 +49,20 @@ def __init__( self.safe_inference_mode = safe_inference_mode self.dtype = dtype - @property def nBits(self): - warn("nBits will be replace by fpSize, due to changes harmonization!", DeprecationWarning) + warn( + "nBits will be replace by fpSize, due to changes harmonization!", + DeprecationWarning, + ) return self.fpSize @nBits.setter def nBits(self, nBits): - warn("nBits will be replace by fpSize, due to changes harmonization!", DeprecationWarning) + warn( + "nBits will be replace by fpSize, due to changes harmonization!", + DeprecationWarning, + ) self.fpSize = nBits def _get_column_prefix(self) -> str: @@ -193,554 +202,6 @@ def transform(self, X, y=None): return arr -class MACCSKeysFingerprintTransformer(FpsTransformer): - def __init__( - self, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = np.int8, - fpSize=167, - ): - """MACCS keys fingerprinter - calculates the 167 fixed MACCS keys - """ - super().__init__( - parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype - ) - if fpSize != 167: - raise ValueError( - "fpSize can only be 167, matching the number of defined MACCS keys!" - ) - self._fpSize = fpSize - - @property - def fpSize(self): - return self._fpSize - - @fpSize.setter - def fpSize(self, fpSize): - if fpSize != 167: - raise ValueError( - "fpSize can only be 167, matching the number of defined MACCS keys!" - ) - self._fpSize = fpSize - - def _mol2fp(self, mol): - return rdMolDescriptors.GetMACCSKeysFingerprint(mol) - - -class MHFingerprintTransformer(FpsTransformer): - def __init__( - self, - radius: int = 3, - rings: bool = True, - isomeric: bool = False, - kekulize: bool = False, - min_radius: int = 1, - fpSize: int = 2048, - seed: int = 42, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = np.int32, - ): - """Transforms the RDKit mol into the MinHash fingerprint (MHFP) - - https://jcheminf.biomedcentral.com/articles/10.1186/s13321-018-0321-8 - - Args: - radius (int, optional): The MHFP radius. Defaults to 3. - rings (bool, optional): Whether or not to include rings in the shingling. Defaults to True. - isomeric (bool, optional): Whether the isomeric SMILES to be considered. Defaults to False. - kekulize (bool, optional): Whether or not to kekulize the extracted SMILES. Defaults to False. - min_radius (int, optional): The minimum radius that is used to extract n-gram. Defaults to 1. - fpSize (int, optional): The number of permutations used for hashing. Defaults to 2048, - this is effectively the length of the FP - seed (int, optional): The value used to seed numpy.random. Defaults to 0. - """ - super().__init__( - parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype - ) - self.radius = radius - self.rings = rings - self.isomeric = isomeric - self.kekulize = kekulize - self.min_radius = min_radius - # Set the .n_permutations and .seed without creating the encoder twice - self.fpSize = fpSize - self._seed = seed - # create the encoder instance - self._recreate_encoder() - - def __getstate__(self): - # Get the state of the parent class - state = super().__getstate__() - # Remove the unpicklable property from the state - state.pop("mhfp_encoder", None) # mhfp_encoder is not picklable - return state - - def __setstate__(self, state): - # Restore the state of the parent class - super().__setstate__(state) - # Re-create the unpicklable property - self._recreate_encoder() - - def _mol2fp(self, mol): - fp = self.mhfp_encoder.EncodeMol( - mol, self.radius, self.rings, self.isomeric, self.kekulize, self.min_radius - ) - return fp - - def _fp2array(self, fp): - return np.array(fp) - - def _recreate_encoder(self): - self.mhfp_encoder = rdMHFPFingerprint.MHFPEncoder( - self.fpSize, self._seed - ) - - @property - def seed(self): - return self._seed - - @seed.setter - def seed(self, seed): - self._seed = seed - # each time the seed parameter is modified refresh an instance of the encoder - self._recreate_encoder() - - @property - def n_permutations(self): - warn("n_permutations will be replace by fpSize, due to changes harmonization!", DeprecationWarning) - return self.fpSize - - @n_permutations.setter - def n_permutations(self, n_permutations): - warn("n_permutations will be replace by fpSize, due to changes harmonization!", DeprecationWarning) - self.fpSize = n_permutations - # each time the n_permutations parameter is modified refresh an instance of the encoder - self._recreate_encoder() - - -class SECFingerprintTransformer(FpsTransformer): - # https://jcheminf.biomedcentral.com/articles/10.1186/s13321-018-0321-8 - def __init__( - self, - radius: int = 3, - rings: bool = True, - isomeric: bool = False, - kekulize: bool = False, - min_radius: int = 1, - fpSize: int = 2048, - n_permutations: int = 0, - seed: int = 0, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = np.int8, - ): - """Transforms the RDKit mol into the SMILES extended connectivity fingerprint (SECFP) - - Args: - radius (int, optional): The MHFP radius. Defaults to 3. - rings (bool, optional): Whether or not to include rings in the shingling. Defaults to True. - isomeric (bool, optional): Whether the isomeric SMILES to be considered. Defaults to False. - kekulize (bool, optional): Whether or not to kekulize the extracted SMILES. Defaults to False. - min_radius (int, optional): The minimum radius that is used to extract n-gram. Defaults to 1. - fpSize (int, optional): The length of the folded fingerprint. Defaults to 2048. - n_permutations (int, optional): The number of permutations used for hashing. Defaults to 0. - seed (int, optional): The value used to seed numpy.random. Defaults to 0. - """ - super().__init__( - parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype - ) - self.radius = radius - self.rings = rings - self.isomeric = isomeric - self.kekulize = kekulize - self.min_radius = min_radius - self.fpSize = fpSize - # Set the .n_permutations and seed without creating the encoder twice - self._n_permutations = n_permutations - self._seed = seed - # create the encoder instance - self._recreate_encoder() - - def __getstate__(self): - # Get the state of the parent class - state = super().__getstate__() - # Remove the unpicklable property from the state - state.pop("mhfp_encoder", None) # mhfp_encoder is not picklable - return state - - def __setstate__(self, state): - # Restore the state of the parent class - super().__setstate__(state) - # Re-create the unpicklable property - self._recreate_encoder() - - def _mol2fp(self, mol): - return self.mhfp_encoder.EncodeSECFPMol( - mol, - self.radius, - self.rings, - self.isomeric, - self.kekulize, - self.min_radius, - self.length, - ) - - def _recreate_encoder(self): - self.mhfp_encoder = rdMHFPFingerprint.MHFPEncoder( - self._n_permutations, self._seed - ) - - @property - def seed(self): - return self._seed - - @seed.setter - def seed(self, seed): - self._seed = seed - # each time the seed parameter is modified refresh an instace of the encoder - self._recreate_encoder() - - @property - def n_permutations(self): - return self._n_permutations - - @n_permutations.setter - def n_permutations(self, n_permutations): - self._n_permutations = n_permutations - # each time the n_permutations parameter is modified refresh an instace of the encoder - self._recreate_encoder() - - @property - def length(self): - warn("length will be replace by fpSize, due to changes harmonization!", DeprecationWarning) - return self.fpSize - - -class AvalonFingerprintTransformer(FpsTransformer): - # Fingerprint from the Avalon toolkeit, https://doi.org/10.1021/ci050413p - def __init__( - self, - fpSize: int = 512, - isQuery: bool = False, - resetVect: bool = False, - bitFlags: int = 15761407, - useCounts: bool = False, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = np.int8, - ): - """Transform RDKit mols into Count or bit-based Avalon Fingerprints - - Parameters - ---------- - fpSize : int, optional - Size of the fingerprint, by default 512 - isQuery : bool, optional - use the fingerprint for a query structure, by default False - resetVect : bool, optional - reset vector, by default False NB: only used in GetAvalonFP (not for GetAvalonCountFP) - bitFlags : int, optional - Substructure fingerprint (32767) or similarity fingerprint (15761407) by default 15761407 - useCounts : bool, optional - If toggled will create the count and not bit-based fingerprint, by default False - """ - super().__init__( - parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype - ) - self.fpSize = fpSize - self.isQuery = isQuery - self.resetVect = resetVect - self.bitFlags = bitFlags - self.useCounts = useCounts - - def _mol2fp(self, mol): - if self.useCounts: - return pyAvalonTools.GetAvalonCountFP( - mol, - nBits=int(self.fpSize), - isQuery=bool(self.isQuery), - bitFlags=int(self.bitFlags), - ) - else: - return pyAvalonTools.GetAvalonFP( - mol, - nBits=int(self.fpSize), - isQuery=bool(self.isQuery), - resetVect=bool(self.resetVect), - bitFlags=int(self.bitFlags), - ) - - -class MorganFingerprintTransformer(FpsTransformer): - def __init__( - self, - fpSize=2048, - radius=2, - useChirality=False, - useBondTypes=True, - useFeatures=False, - useCounts=False, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = np.int8, - ): - """Transform RDKit mols into Count or bit-based hashed MorganFingerprints - - Parameters - ---------- - fpSize : int, optional - Size of the hashed fingerprint, by default 2048 - radius : int, optional - Radius of the fingerprint, by default 2 - useChirality : bool, optional - Include chirality in calculation of the fingerprint keys, by default False - useBondTypes : bool, optional - Include bondtypes in calculation of the fingerprint keys, by default True - useFeatures : bool, optional - use chemical features, rather than atom-type in calculation of the fingerprint keys, by default False - useCounts : bool, optional - If toggled will create the count and not bit-based fingerprint, by default False - """ - super().__init__( - parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype - ) - self.fpSize = fpSize - self.radius = radius - self.useChirality = useChirality - self.useBondTypes = useBondTypes - self.useFeatures = useFeatures - self.useCounts = useCounts - - warn("MorganFingerprintTransformer will be replace by MorganGeneratorTransformer, due to changes in RDKit!", DeprecationWarning) - - def _mol2fp(self, mol): - if self.useCounts: - return rdMolDescriptors.GetHashedMorganFingerprint( - mol, - int(self.radius), - nBits=int(self.fpSize), - useFeatures=bool(self.useFeatures), - useChirality=bool(self.useChirality), - useBondTypes=bool(self.useBondTypes), - ) - else: - return rdMolDescriptors.GetMorganFingerprintAsBitVect( - mol, - int(self.radius), - nBits=int(self.fpSize), - useFeatures=bool(self.useFeatures), - useChirality=bool(self.useChirality), - useBondTypes=bool(self.useBondTypes), - ) - - -class RDKitFingerprintTransformer(FpsTransformer): - def __init__( - self, - minPath: int = 1, - maxPath: int = 7, - useHs: bool = True, - branchedPaths: bool = True, - useBondOrder: bool = True, - countSimulation: bool = False, - countBounds=None, - fpSize: int = 2048, - numBitsPerFeature: int = 2, - atomInvariantsGenerator=None, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = np.int8, - ): - """Calculates the RDKit fingerprints - - Parameters - ---------- - minPath : int, optional - the minimum path length (in bonds) to be included, by default 1 - maxPath : int, optional - the maximum path length (in bonds) to be included, by default 7 - useHs : bool, optional - toggles inclusion of Hs in paths (if the molecule has explicit Hs), by default True - branchedPaths : bool, optional - toggles generation of branched subgraphs, not just linear paths, by default True - useBondOrder : bool, optional - toggles inclusion of bond orders in the path hashes, by default True - countSimulation : bool, optional - if set, use count simulation while generating the fingerprint, by default False - countBounds : _type_, optional - boundaries for count simulation, corresponding bit will be set if the count is higher than the number provided for that spot, by default None - fpSize : int, optional - size of the generated fingerprint, does not affect the sparse versions, by default 2048 - numBitsPerFeature : int, optional - the number of bits set per path/subgraph found, by default 2 - atomInvariantsGenerator : _type_, optional - atom invariants to be used during fingerprint generation, by default None - """ - super().__init__( - parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype - ) - self.minPath = minPath - self.maxPath = maxPath - self.useHs = useHs - self.branchedPaths = branchedPaths - self.useBondOrder = useBondOrder - self.countSimulation = countSimulation - self.countBounds = countBounds - self.fpSize = fpSize - self.numBitsPerFeature = numBitsPerFeature - self.atomInvariantsGenerator = atomInvariantsGenerator - - warn("RDKitFingerprintTransformer will be replace by RDKitFPGeneratorTransformer, due to changes in RDKit!", DeprecationWarning) - - - def _mol2fp(self, mol): - generator = rdFingerprintGenerator.GetRDKitFPGenerator( - minPath=int(self.minPath), - maxPath=int(self.maxPath), - useHs=bool(self.useHs), - branchedPaths=bool(self.branchedPaths), - useBondOrder=bool(self.useBondOrder), - countSimulation=bool(self.countSimulation), - countBounds=bool(self.countBounds), - fpSize=int(self.fpSize), - numBitsPerFeature=int(self.numBitsPerFeature), - atomInvariantsGenerator=self.atomInvariantsGenerator, - ) - return generator.GetFingerprint(mol) - - -class AtomPairFingerprintTransformer(FpsTransformer): - def __init__( - self, - minLength: int = 1, - maxLength: int = 30, - fromAtoms=0, - ignoreAtoms=0, - atomInvariants=0, - nBitsPerEntry: int = 4, - includeChirality: bool = False, - use2D: bool = True, - confId: int = -1, - fpSize=2048, - useCounts: bool = False, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = np.int8, - ): - super().__init__( - parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype - ) - self.minLength = minLength - self.maxLength = maxLength - self.fromAtoms = fromAtoms - self.ignoreAtoms = ignoreAtoms - self.atomInvariants = atomInvariants - self.includeChirality = includeChirality - self.use2D = use2D - self.confId = confId - self.fpSize = fpSize - self.nBitsPerEntry = nBitsPerEntry - self.useCounts = useCounts - - warn("AtomPairFingerprintTransformer will be replace by AtomPairFPGeneratorTransformer, due to changes in RDKit!", DeprecationWarning) - - def _mol2fp(self, mol): - if self.useCounts: - return rdMolDescriptors.GetHashedAtomPairFingerprint( - mol, - nBits=int(self.fpSize), - minLength=int(self.minLength), - maxLength=int(self.maxLength), - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - atomInvariants=self.atomInvariants, - includeChirality=bool(self.includeChirality), - use2D=bool(self.use2D), - confId=int(self.confId), - ) - else: - return rdMolDescriptors.GetHashedAtomPairFingerprintAsBitVect( - mol, - nBits=int(self.fpSize), - minLength=int(self.minLength), - maxLength=int(self.maxLength), - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - atomInvariants=self.atomInvariants, - nBitsPerEntry=int(self.nBitsPerEntry), - includeChirality=bool(self.includeChirality), - use2D=bool(self.use2D), - confId=int(self.confId), - ) - - -class TopologicalTorsionFingerprintTransformer(FpsTransformer): - def __init__( - self, - targetSize: int = 4, - fromAtoms=0, - ignoreAtoms=0, - atomInvariants=0, - includeChirality: bool = False, - nBitsPerEntry: int = 4, - fpSize=2048, - useCounts: bool = False, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = np.int8, - ): - super().__init__( - parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype - ) - self.targetSize = targetSize - self.fromAtoms = fromAtoms - self.ignoreAtoms = ignoreAtoms - self.atomInvariants = atomInvariants - self.includeChirality = includeChirality - self.nBitsPerEntry = nBitsPerEntry - self.fpSize = fpSize - self.useCounts = useCounts - - warn("TopologicalTorsionFingerprintTransformer will be replace by TopologicalTorsionFPGeneatorTransformer, due to changes in RDKit!", DeprecationWarning) - - def _mol2fp(self, mol): - if self.useCounts: - return rdMolDescriptors.GetHashedTopologicalTorsionFingerprint( - mol, - nBits=int(self.fpSize), - targetSize=int(self.targetSize), - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - atomInvariants=self.atomInvariants, - includeChirality=bool(self.includeChirality), - ) - else: - return rdMolDescriptors.GetHashedTopologicalTorsionFingerprintAsBitVect( - mol, - nBits=int(self.fpSize), - targetSize=int(self.targetSize), - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - atomInvariants=self.atomInvariants, - includeChirality=bool(self.includeChirality), - nBitsPerEntry=int(self.nBitsPerEntry), - ) - - -def parallel_helper(args): - """Parallel_helper takes a tuple with classname, the objects parameters and the mols to process. - Then instantiates the class with the parameters and processes the mol. - Intention is to be able to do this in child processes as some classes can't be pickled""" - classname, parameters, X_mols = args - from scikit_mol import fingerprints - - transformer = getattr(fingerprints, classname)(**parameters) - return transformer._transform(X_mols) - - class FpsGeneratorTransformer(FpsTransformer): _regenerate_on_properties = () @@ -755,22 +216,29 @@ def __getstate__(self): state = super().__getstate__() state.update(self.get_params()) # Remove the unpicklable property from the state - state.pop("_fpgen", None) # fpgen is not picklable + state.pop("_fpgen", None) # fpgen is not picklable return state def __setstate__(self, state): # Restore the state of the parent class super().__setstate__(state) # Re-create the unpicklable property - generatort_keys = inspect.signature(self._generate_fp_generator).parameters.keys() - params = [setattr(self, k, state["_"+k]) if "_"+k in state else setattr(self, k, state[k]) for k in generatort_keys] + generatort_keys = inspect.signature( + self._generate_fp_generator + ).parameters.keys() + params = [ + setattr(self, k, state["_" + k]) + if "_" + k in state + else setattr(self, k, state[k]) + for k in generatort_keys + ] self._generate_fp_generator() def __setattr__(self, name: str, value): super().__setattr__(name, value) if ( - not hasattr(self, "_initializing") - and name in self._regenerate_on_properties + not hasattr(self, "_initializing") + and name in self._regenerate_on_properties ): self._generate_fp_generator() @@ -787,190 +255,12 @@ def _transform_mol(self, mol) -> np.array: raise NotImplementedError("_transform_mol not implemented") -class MorganFPGeneratorTransformer(FpsGeneratorTransformer): - _regenerate_on_properties = ("radius", "fpSize", "useChirality", "useFeatures", "useBondTypes") - - def __init__(self, fpSize=2048, radius=2, useChirality=False, - useBondTypes=True, useFeatures=False, useCounts=False, - parallel: Union[bool, int] = False, ): - """Transform RDKit mols into Count or bit-based hashed MorganFingerprints - - Parameters - ---------- - fpsize : int, optional - Size of the hashed fingerprint, by default 2048 - radius : int, optional - Radius of the fingerprint, by default 2 - useChirality : bool, optional - Include chirality in calculation of the fingerprint keys, by default False - useBondTypes : bool, optional - Include bondtypes in calculation of the fingerprint keys, by default True - useFeatures : bool, optional - use chemical features, rather than atom-type in calculation of the fingerprint keys, by default False - useCounts : bool, optional - If toggled will create the count and not bit-based fingerprint, by default False - """ - - self._initializing = True - super().__init__(parallel = parallel) - self.fpSize = fpSize - self.radius = radius - self.useChirality = useChirality - self.useFeatures = useFeatures - self.useCounts = useCounts - self.useBondTypes = useBondTypes - - self._generate_fp_generator() - delattr(self, "_initializing") - - - def _generate_fp_generator(self): - - if self.useFeatures: - atomInvariantsGenerator = GetMorganFeatureAtomInvGen() - else: - atomInvariantsGenerator = None - - self._fpgen = GetMorganGenerator(radius=self.radius, - fpSize=self.fpSize, - includeChirality=self.useChirality, - useBondTypes=self.useBondTypes, - atomInvariantsGenerator=atomInvariantsGenerator, - ) - - def _transform_mol(self, mol) -> np.array: - if self.useCounts: - return self._fpgen.GetCountFingerprintAsNumPy(mol) - else: - return self._fpgen.GetFingerprintAsNumPy(mol) - - -class TopologicalTorsionFPGeneatorTransformer(FpsGeneratorTransformer): - _regenerate_on_properties = ("fpSize", "includeChirality", "targetSize") - - def __init__(self, targetSize:int = 4, fromAtoms = None, ignoreAtoms = None, atomInvariants = None, confId=-1, - includeChirality:bool = False, fpSize:int=2048, - useCounts:bool=False, parallel: Union[bool, int] = False): - - self._initializing = True - super().__init__(parallel=parallel) - self.fpSize = fpSize - self.includeChirality = includeChirality - self.targetSize = targetSize - - self.fromAtoms = fromAtoms - self.ignoreAtoms = ignoreAtoms - self.atomInvariants = atomInvariants - self.confId = confId - self.useCounts = useCounts - - self._generate_fp_generator() - delattr(self, "_initializing") - - - def _generate_fp_generator(self): - self._fpgen = GetTopologicalTorsionGenerator(torsionAtomCount=self.targetSize, includeChirality=self.includeChirality, - fpSize=self.fpSize) - - def _transform_mol(self, mol) -> np.array: - if self.useCounts: - return self._fpgen.GetCountFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self.ignoreAtoms, customAtomInvariants=self.atomInvariants) - else: - return self._fpgen.GetFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self.ignoreAtoms, customAtomInvariants=self.atomInvariants) - - -class AtomPairFPGeneratorTransformer(FpsGeneratorTransformer): - _regenerate_on_properties = ("fpSize", "includeChirality", "use2D", "minLength", "maxLength") - - def __init__(self, minLength:int = 1, maxLength:int = 30, fromAtoms = None, ignoreAtoms = None, atomInvariants = None, - includeChirality:bool = False, use2D:bool = True, confId:int = -1, fpSize:int=2048, - useCounts:bool=False, parallel: Union[bool, int] = False,): - self._initializing = True - super().__init__(parallel = parallel) - self.fpSize = fpSize - self.use2D = use2D - self.includeChirality = includeChirality - self.minLength = minLength - self.maxLength = maxLength - - self.useCounts= useCounts - self.confId = confId - self.fromAtoms = fromAtoms - self.ignoreAtoms = ignoreAtoms - self.atomInvariants = atomInvariants - - self._generate_fp_generator() - delattr(self, "_initializing") - - def _generate_fp_generator(self): - self._fpgen = GetAtomPairGenerator(minDistance=self.minLength, maxDistance=self.maxLength, - includeChirality=self.includeChirality, - use2D=self.use2D, fpSize=self.fpSize) - - def _transform_mol(self, mol) -> np.array: - if self.useCounts: - return self._fpgen.GetCountFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self.ignoreAtoms, customAtomInvariants=self.atomInvariants) - else: - return self._fpgen.GetFingerprintAsNumPy(mol, fromAtoms=self.fromAtoms, ignoreAtoms=self.ignoreAtoms, customAtomInvariants=self.atomInvariants) - - -class RDKitFPGeneratorTransformer(FpsGeneratorTransformer): - _regenerate_on_properties = ("minPath", "maxPath", "useHs", "branchedPaths", "useBondOrder", "countSimulation", "fpSize", "countBounds", - "numBitsPerFeature") - - def __init__(self, minPath:int = 1, maxPath:int =7, useHs:bool = True, branchedPaths:bool = True, - useBondOrder:bool = True, countSimulation:bool = False, countBounds = None, - fpSize:int = 2048, numBitsPerFeature:int = 2, - useCounts:bool = False, parallel: Union[bool, int] = False - ): - """Calculates the RDKit fingerprints - - Parameters - ---------- - minPath : int, optional - the minimum path length (in bonds) to be included, by default 1 - maxPath : int, optional - the maximum path length (in bonds) to be included, by default 7 - useHs : bool, optional - toggles inclusion of Hs in paths (if the molecule has explicit Hs), by default True - branchedPaths : bool, optional - toggles generation of branched subgraphs, not just linear paths, by default True - useBondOrder : bool, optional - toggles inclusion of bond orders in the path hashes, by default True - countSimulation : bool, optional - if set, use count simulation while generating the fingerprint, by default False - countBounds : _type_, optional - boundaries for count simulation, corresponding bit will be set if the count is higher than the number provided for that spot, by default None - fpSize : int, optional - size of the generated fingerprint, does not affect the sparse versions, by default 2048 - numBitsPerFeature : int, optional - the number of bits set per path/subgraph found, by default 2 - """ - self._initializing = True - super().__init__(parallel = parallel) - self.minPath = minPath - self.maxPath = maxPath - self.useHs = useHs - self.branchedPaths = branchedPaths - self.useBondOrder = useBondOrder - self.countSimulation = countSimulation - self.fpSize = fpSize - self.numBitsPerFeature = numBitsPerFeature - self.countBounds = countBounds - - self.useCounts = useCounts - - self._generate_fp_generator() - delattr(self, "_initializing") - - def _transform_mol(self, mol) -> np.array: - if self.useCounts: - return self._fpgen.GetCountFingerprintAsNumPy(mol) - else: - return self._fpgen.GetFingerprintAsNumPy(mol) +def parallel_helper(args): + """Parallel_helper takes a tuple with classname, the objects parameters and the mols to process. + Then instantiates the class with the parameters and processes the mol. + Intention is to be able to do this in child processes as some classes can't be pickled""" + classname, parameters, X_mols = args + from scikit_mol import fingerprints - def _generate_fp_generator(self): - self._fpgen = GetRDKitFPGenerator(minPath=self.minPath, maxPath=self.maxPath, useHs=self.useHs, - branchedPaths=self.branchedPaths,useBondOrder=self.useBondOrder, - countSimulation=self.countSimulation, fpSize=self.fpSize, - countBounds=self.countBounds, numBitsPerFeature=self.numBitsPerFeature) + transformer = getattr(fingerprints, classname)(**parameters) + return transformer._transform(X_mols) diff --git a/scikit_mol/fingerprints/maccs.py b/scikit_mol/fingerprints/maccs.py new file mode 100644 index 0000000..ca38966 --- /dev/null +++ b/scikit_mol/fingerprints/maccs.py @@ -0,0 +1,41 @@ +from typing import Union +from rdkit.Chem import rdMolDescriptors +import numpy as np + +from .baseclasses import FpsTransformer + + +class MACCSKeysFingerprintTransformer(FpsTransformer): + def __init__( + self, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + dtype: np.dtype = np.int8, + fpSize=167, + ): + """MACCS keys fingerprinter + calculates the 167 fixed MACCS keys + """ + super().__init__( + parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype + ) + if fpSize != 167: + raise ValueError( + "fpSize can only be 167, matching the number of defined MACCS keys!" + ) + self._fpSize = fpSize + + @property + def fpSize(self): + return self._fpSize + + @fpSize.setter + def fpSize(self, fpSize): + if fpSize != 167: + raise ValueError( + "fpSize can only be 167, matching the number of defined MACCS keys!" + ) + self._fpSize = fpSize + + def _mol2fp(self, mol): + return rdMolDescriptors.GetMACCSKeysFingerprint(mol) diff --git a/scikit_mol/fingerprints/minhash.py b/scikit_mol/fingerprints/minhash.py new file mode 100644 index 0000000..1c7e62a --- /dev/null +++ b/scikit_mol/fingerprints/minhash.py @@ -0,0 +1,206 @@ +from typing import Union + +import numpy as np + +from warnings import warn + +from .baseclasses import FpsTransformer + +from rdkit.Chem import rdMHFPFingerprint + + +class MHFingerprintTransformer(FpsTransformer): + def __init__( + self, + radius: int = 3, + rings: bool = True, + isomeric: bool = False, + kekulize: bool = False, + min_radius: int = 1, + fpSize: int = 2048, + seed: int = 42, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + dtype: np.dtype = np.int32, + ): + """Transforms the RDKit mol into the MinHash fingerprint (MHFP) + + https://jcheminf.biomedcentral.com/articles/10.1186/s13321-018-0321-8 + + Args: + radius (int, optional): The MHFP radius. Defaults to 3. + rings (bool, optional): Whether or not to include rings in the shingling. Defaults to True. + isomeric (bool, optional): Whether the isomeric SMILES to be considered. Defaults to False. + kekulize (bool, optional): Whether or not to kekulize the extracted SMILES. Defaults to False. + min_radius (int, optional): The minimum radius that is used to extract n-gram. Defaults to 1. + fpSize (int, optional): The number of permutations used for hashing. Defaults to 2048, + this is effectively the length of the FP + seed (int, optional): The value used to seed numpy.random. Defaults to 0. + """ + super().__init__( + parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype + ) + self.radius = radius + self.rings = rings + self.isomeric = isomeric + self.kekulize = kekulize + self.min_radius = min_radius + # Set the .n_permutations and .seed without creating the encoder twice + self.fpSize = fpSize + self._seed = seed + # create the encoder instance + self._recreate_encoder() + + def __getstate__(self): + # Get the state of the parent class + state = super().__getstate__() + # Remove the unpicklable property from the state + state.pop("mhfp_encoder", None) # mhfp_encoder is not picklable + return state + + def __setstate__(self, state): + # Restore the state of the parent class + super().__setstate__(state) + # Re-create the unpicklable property + self._recreate_encoder() + + def _mol2fp(self, mol): + fp = self.mhfp_encoder.EncodeMol( + mol, self.radius, self.rings, self.isomeric, self.kekulize, self.min_radius + ) + return fp + + def _fp2array(self, fp): + return np.array(fp) + + def _recreate_encoder(self): + self.mhfp_encoder = rdMHFPFingerprint.MHFPEncoder(self.fpSize, self._seed) + + @property + def seed(self): + return self._seed + + @seed.setter + def seed(self, seed): + self._seed = seed + # each time the seed parameter is modified refresh an instance of the encoder + self._recreate_encoder() + + @property + def n_permutations(self): + warn( + "n_permutations will be replace by fpSize, due to changes harmonization!", + DeprecationWarning, + ) + return self.fpSize + + @n_permutations.setter + def n_permutations(self, n_permutations): + warn( + "n_permutations will be replace by fpSize, due to changes harmonization!", + DeprecationWarning, + ) + self.fpSize = n_permutations + # each time the n_permutations parameter is modified refresh an instance of the encoder + self._recreate_encoder() + + +class SECFingerprintTransformer(FpsTransformer): + # https://jcheminf.biomedcentral.com/articles/10.1186/s13321-018-0321-8 + def __init__( + self, + radius: int = 3, + rings: bool = True, + isomeric: bool = False, + kekulize: bool = False, + min_radius: int = 1, + fpSize: int = 2048, + n_permutations: int = 0, + seed: int = 0, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + dtype: np.dtype = np.int8, + ): + """Transforms the RDKit mol into the SMILES extended connectivity fingerprint (SECFP) + + Args: + radius (int, optional): The MHFP radius. Defaults to 3. + rings (bool, optional): Whether or not to include rings in the shingling. Defaults to True. + isomeric (bool, optional): Whether the isomeric SMILES to be considered. Defaults to False. + kekulize (bool, optional): Whether or not to kekulize the extracted SMILES. Defaults to False. + min_radius (int, optional): The minimum radius that is used to extract n-gram. Defaults to 1. + fpSize (int, optional): The length of the folded fingerprint. Defaults to 2048. + n_permutations (int, optional): The number of permutations used for hashing. Defaults to 0. + seed (int, optional): The value used to seed numpy.random. Defaults to 0. + """ + super().__init__( + parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype + ) + self.radius = radius + self.rings = rings + self.isomeric = isomeric + self.kekulize = kekulize + self.min_radius = min_radius + self.fpSize = fpSize + # Set the .n_permutations and seed without creating the encoder twice + self._n_permutations = n_permutations + self._seed = seed + # create the encoder instance + self._recreate_encoder() + + def __getstate__(self): + # Get the state of the parent class + state = super().__getstate__() + # Remove the unpicklable property from the state + state.pop("mhfp_encoder", None) # mhfp_encoder is not picklable + return state + + def __setstate__(self, state): + # Restore the state of the parent class + super().__setstate__(state) + # Re-create the unpicklable property + self._recreate_encoder() + + def _mol2fp(self, mol): + return self.mhfp_encoder.EncodeSECFPMol( + mol, + self.radius, + self.rings, + self.isomeric, + self.kekulize, + self.min_radius, + self.length, + ) + + def _recreate_encoder(self): + self.mhfp_encoder = rdMHFPFingerprint.MHFPEncoder( + self._n_permutations, self._seed + ) + + @property + def seed(self): + return self._seed + + @seed.setter + def seed(self, seed): + self._seed = seed + # each time the seed parameter is modified refresh an instace of the encoder + self._recreate_encoder() + + @property + def n_permutations(self): + return self._n_permutations + + @n_permutations.setter + def n_permutations(self, n_permutations): + self._n_permutations = n_permutations + # each time the n_permutations parameter is modified refresh an instace of the encoder + self._recreate_encoder() + + @property + def length(self): + warn( + "length will be replace by fpSize, due to changes harmonization!", + DeprecationWarning, + ) + return self.fpSize diff --git a/scikit_mol/fingerprints/morgan.py b/scikit_mol/fingerprints/morgan.py new file mode 100644 index 0000000..37d7cf8 --- /dev/null +++ b/scikit_mol/fingerprints/morgan.py @@ -0,0 +1,150 @@ +from typing import Union + +from rdkit.Chem import rdMolDescriptors + +import numpy as np + +from warnings import warn + +from rdkit.Chem.rdFingerprintGenerator import ( + GetMorganGenerator, + GetMorganFeatureAtomInvGen, +) + +from .baseclasses import FpsTransformer, FpsGeneratorTransformer + + +class MorganFingerprintTransformer(FpsTransformer): + def __init__( + self, + fpSize=2048, + radius=2, + useChirality=False, + useBondTypes=True, + useFeatures=False, + useCounts=False, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + dtype: np.dtype = np.int8, + ): + """Transform RDKit mols into Count or bit-based hashed MorganFingerprints + + Parameters + ---------- + fpSize : int, optional + Size of the hashed fingerprint, by default 2048 + radius : int, optional + Radius of the fingerprint, by default 2 + useChirality : bool, optional + Include chirality in calculation of the fingerprint keys, by default False + useBondTypes : bool, optional + Include bondtypes in calculation of the fingerprint keys, by default True + useFeatures : bool, optional + use chemical features, rather than atom-type in calculation of the fingerprint keys, by default False + useCounts : bool, optional + If toggled will create the count and not bit-based fingerprint, by default False + """ + super().__init__( + parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype + ) + self.fpSize = fpSize + self.radius = radius + self.useChirality = useChirality + self.useBondTypes = useBondTypes + self.useFeatures = useFeatures + self.useCounts = useCounts + + warn( + "MorganFingerprintTransformer will be replace by MorganGeneratorTransformer, due to changes in RDKit!", + DeprecationWarning, + ) + + def _mol2fp(self, mol): + if self.useCounts: + return rdMolDescriptors.GetHashedMorganFingerprint( + mol, + int(self.radius), + nBits=int(self.fpSize), + useFeatures=bool(self.useFeatures), + useChirality=bool(self.useChirality), + useBondTypes=bool(self.useBondTypes), + ) + else: + return rdMolDescriptors.GetMorganFingerprintAsBitVect( + mol, + int(self.radius), + nBits=int(self.fpSize), + useFeatures=bool(self.useFeatures), + useChirality=bool(self.useChirality), + useBondTypes=bool(self.useBondTypes), + ) + + +class MorganFPGeneratorTransformer(FpsGeneratorTransformer): + _regenerate_on_properties = ( + "radius", + "fpSize", + "useChirality", + "useFeatures", + "useBondTypes", + ) + + def __init__( + self, + fpSize=2048, + radius=2, + useChirality=False, + useBondTypes=True, + useFeatures=False, + useCounts=False, + parallel: Union[bool, int] = False, + ): + """Transform RDKit mols into Count or bit-based hashed MorganFingerprints + + Parameters + ---------- + fpsize : int, optional + Size of the hashed fingerprint, by default 2048 + radius : int, optional + Radius of the fingerprint, by default 2 + useChirality : bool, optional + Include chirality in calculation of the fingerprint keys, by default False + useBondTypes : bool, optional + Include bondtypes in calculation of the fingerprint keys, by default True + useFeatures : bool, optional + use chemical features, rather than atom-type in calculation of the fingerprint keys, by default False + useCounts : bool, optional + If toggled will create the count and not bit-based fingerprint, by default False + """ + + self._initializing = True + super().__init__(parallel=parallel) + self.fpSize = fpSize + self.radius = radius + self.useChirality = useChirality + self.useFeatures = useFeatures + self.useCounts = useCounts + self.useBondTypes = useBondTypes + + self._generate_fp_generator() + delattr(self, "_initializing") + + def _generate_fp_generator(self): + if self.useFeatures: + atomInvariantsGenerator = GetMorganFeatureAtomInvGen() + else: + atomInvariantsGenerator = None + + self._fpgen = GetMorganGenerator( + radius=self.radius, + fpSize=self.fpSize, + includeChirality=self.useChirality, + useBondTypes=self.useBondTypes, + atomInvariantsGenerator=atomInvariantsGenerator, + ) + + def _transform_mol(self, mol) -> np.array: + if self.useCounts: + return self._fpgen.GetCountFingerprintAsNumPy(mol) + else: + return self._fpgen.GetFingerprintAsNumPy(mol) diff --git a/scikit_mol/fingerprints/rdkitfp.py b/scikit_mol/fingerprints/rdkitfp.py new file mode 100644 index 0000000..28ce0a8 --- /dev/null +++ b/scikit_mol/fingerprints/rdkitfp.py @@ -0,0 +1,175 @@ +from typing import Union + +import numpy as np + +from warnings import warn + +from .baseclasses import FpsTransformer, FpsGeneratorTransformer + +from rdkit.Chem.rdFingerprintGenerator import GetRDKitFPGenerator + +from rdkit.Chem import rdFingerprintGenerator + + +class RDKitFingerprintTransformer(FpsTransformer): + def __init__( + self, + minPath: int = 1, + maxPath: int = 7, + useHs: bool = True, + branchedPaths: bool = True, + useBondOrder: bool = True, + countSimulation: bool = False, + countBounds=None, + fpSize: int = 2048, + numBitsPerFeature: int = 2, + atomInvariantsGenerator=None, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + dtype: np.dtype = np.int8, + ): + """Calculates the RDKit fingerprints + + Parameters + ---------- + minPath : int, optional + the minimum path length (in bonds) to be included, by default 1 + maxPath : int, optional + the maximum path length (in bonds) to be included, by default 7 + useHs : bool, optional + toggles inclusion of Hs in paths (if the molecule has explicit Hs), by default True + branchedPaths : bool, optional + toggles generation of branched subgraphs, not just linear paths, by default True + useBondOrder : bool, optional + toggles inclusion of bond orders in the path hashes, by default True + countSimulation : bool, optional + if set, use count simulation while generating the fingerprint, by default False + countBounds : _type_, optional + boundaries for count simulation, corresponding bit will be set if the count is higher than the number provided for that spot, by default None + fpSize : int, optional + size of the generated fingerprint, does not affect the sparse versions, by default 2048 + numBitsPerFeature : int, optional + the number of bits set per path/subgraph found, by default 2 + atomInvariantsGenerator : _type_, optional + atom invariants to be used during fingerprint generation, by default None + """ + super().__init__( + parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype + ) + self.minPath = minPath + self.maxPath = maxPath + self.useHs = useHs + self.branchedPaths = branchedPaths + self.useBondOrder = useBondOrder + self.countSimulation = countSimulation + self.countBounds = countBounds + self.fpSize = fpSize + self.numBitsPerFeature = numBitsPerFeature + self.atomInvariantsGenerator = atomInvariantsGenerator + + warn( + "RDKitFingerprintTransformer will be replace by RDKitFPGeneratorTransformer, due to changes in RDKit!", + DeprecationWarning, + ) + + def _mol2fp(self, mol): + generator = rdFingerprintGenerator.GetRDKitFPGenerator( + minPath=int(self.minPath), + maxPath=int(self.maxPath), + useHs=bool(self.useHs), + branchedPaths=bool(self.branchedPaths), + useBondOrder=bool(self.useBondOrder), + countSimulation=bool(self.countSimulation), + countBounds=bool(self.countBounds), + fpSize=int(self.fpSize), + numBitsPerFeature=int(self.numBitsPerFeature), + atomInvariantsGenerator=self.atomInvariantsGenerator, + ) + return generator.GetFingerprint(mol) + + +class RDKitFPGeneratorTransformer(FpsGeneratorTransformer): + _regenerate_on_properties = ( + "minPath", + "maxPath", + "useHs", + "branchedPaths", + "useBondOrder", + "countSimulation", + "fpSize", + "countBounds", + "numBitsPerFeature", + ) + + def __init__( + self, + minPath: int = 1, + maxPath: int = 7, + useHs: bool = True, + branchedPaths: bool = True, + useBondOrder: bool = True, + countSimulation: bool = False, + countBounds=None, + fpSize: int = 2048, + numBitsPerFeature: int = 2, + useCounts: bool = False, + parallel: Union[bool, int] = False, + ): + """Calculates the RDKit fingerprints + + Parameters + ---------- + minPath : int, optional + the minimum path length (in bonds) to be included, by default 1 + maxPath : int, optional + the maximum path length (in bonds) to be included, by default 7 + useHs : bool, optional + toggles inclusion of Hs in paths (if the molecule has explicit Hs), by default True + branchedPaths : bool, optional + toggles generation of branched subgraphs, not just linear paths, by default True + useBondOrder : bool, optional + toggles inclusion of bond orders in the path hashes, by default True + countSimulation : bool, optional + if set, use count simulation while generating the fingerprint, by default False + countBounds : _type_, optional + boundaries for count simulation, corresponding bit will be set if the count is higher than the number provided for that spot, by default None + fpSize : int, optional + size of the generated fingerprint, does not affect the sparse versions, by default 2048 + numBitsPerFeature : int, optional + the number of bits set per path/subgraph found, by default 2 + """ + self._initializing = True + super().__init__(parallel=parallel) + self.minPath = minPath + self.maxPath = maxPath + self.useHs = useHs + self.branchedPaths = branchedPaths + self.useBondOrder = useBondOrder + self.countSimulation = countSimulation + self.fpSize = fpSize + self.numBitsPerFeature = numBitsPerFeature + self.countBounds = countBounds + + self.useCounts = useCounts + + self._generate_fp_generator() + delattr(self, "_initializing") + + def _transform_mol(self, mol) -> np.array: + if self.useCounts: + return self._fpgen.GetCountFingerprintAsNumPy(mol) + else: + return self._fpgen.GetFingerprintAsNumPy(mol) + + def _generate_fp_generator(self): + self._fpgen = GetRDKitFPGenerator( + minPath=self.minPath, + maxPath=self.maxPath, + useHs=self.useHs, + branchedPaths=self.branchedPaths, + useBondOrder=self.useBondOrder, + countSimulation=self.countSimulation, + fpSize=self.fpSize, + countBounds=self.countBounds, + numBitsPerFeature=self.numBitsPerFeature, + ) diff --git a/scikit_mol/fingerprints/topologicaltorsion.py b/scikit_mol/fingerprints/topologicaltorsion.py new file mode 100644 index 0000000..0b6640d --- /dev/null +++ b/scikit_mol/fingerprints/topologicaltorsion.py @@ -0,0 +1,120 @@ +from typing import Union + +import numpy as np + +from warnings import warn + +from .baseclasses import FpsTransformer, FpsGeneratorTransformer + +from rdkit.Chem import rdMolDescriptors +from rdkit.Chem.rdFingerprintGenerator import GetTopologicalTorsionGenerator + + +class TopologicalTorsionFingerprintTransformer(FpsTransformer): + def __init__( + self, + targetSize: int = 4, + fromAtoms=0, + ignoreAtoms=0, + atomInvariants=0, + includeChirality: bool = False, + nBitsPerEntry: int = 4, + fpSize=2048, + useCounts: bool = False, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + dtype: np.dtype = np.int8, + ): + super().__init__( + parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype + ) + self.targetSize = targetSize + self.fromAtoms = fromAtoms + self.ignoreAtoms = ignoreAtoms + self.atomInvariants = atomInvariants + self.includeChirality = includeChirality + self.nBitsPerEntry = nBitsPerEntry + self.fpSize = fpSize + self.useCounts = useCounts + + warn( + "TopologicalTorsionFingerprintTransformer will be replace by TopologicalTorsionFPGeneatorTransformer, due to changes in RDKit!", + DeprecationWarning, + ) + + def _mol2fp(self, mol): + if self.useCounts: + return rdMolDescriptors.GetHashedTopologicalTorsionFingerprint( + mol, + nBits=int(self.fpSize), + targetSize=int(self.targetSize), + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + atomInvariants=self.atomInvariants, + includeChirality=bool(self.includeChirality), + ) + else: + return rdMolDescriptors.GetHashedTopologicalTorsionFingerprintAsBitVect( + mol, + nBits=int(self.fpSize), + targetSize=int(self.targetSize), + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + atomInvariants=self.atomInvariants, + includeChirality=bool(self.includeChirality), + nBitsPerEntry=int(self.nBitsPerEntry), + ) + + +class TopologicalTorsionFPGeneatorTransformer(FpsGeneratorTransformer): + _regenerate_on_properties = ("fpSize", "includeChirality", "targetSize") + + def __init__( + self, + targetSize: int = 4, + fromAtoms=None, + ignoreAtoms=None, + atomInvariants=None, + confId=-1, + includeChirality: bool = False, + fpSize: int = 2048, + useCounts: bool = False, + parallel: Union[bool, int] = False, + ): + self._initializing = True + super().__init__(parallel=parallel) + self.fpSize = fpSize + self.includeChirality = includeChirality + self.targetSize = targetSize + + self.fromAtoms = fromAtoms + self.ignoreAtoms = ignoreAtoms + self.atomInvariants = atomInvariants + self.confId = confId + self.useCounts = useCounts + + self._generate_fp_generator() + delattr(self, "_initializing") + + def _generate_fp_generator(self): + self._fpgen = GetTopologicalTorsionGenerator( + torsionAtomCount=self.targetSize, + includeChirality=self.includeChirality, + fpSize=self.fpSize, + ) + + def _transform_mol(self, mol) -> np.array: + if self.useCounts: + return self._fpgen.GetCountFingerprintAsNumPy( + mol, + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + customAtomInvariants=self.atomInvariants, + ) + else: + return self._fpgen.GetFingerprintAsNumPy( + mol, + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + customAtomInvariants=self.atomInvariants, + ) From f092febfbdcab45f32088217add98b48ff8e215c Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Fri, 22 Nov 2024 16:15:14 +0100 Subject: [PATCH 12/24] Refactored the baseclasses for more logical inheritance for the two abstract classes --- scikit_mol/fingerprints/baseclasses.py | 89 +++++++---- tests/test_fptransformersgenerator.py | 202 +++++++++++++++---------- 2 files changed, 179 insertions(+), 112 deletions(-) diff --git a/scikit_mol/fingerprints/baseclasses.py b/scikit_mol/fingerprints/baseclasses.py index ce28e18..03ca11b 100644 --- a/scikit_mol/fingerprints/baseclasses.py +++ b/scikit_mol/fingerprints/baseclasses.py @@ -36,18 +36,16 @@ ) -class FpsTransformer(ABC, BaseEstimator, TransformerMixin): +class BaseFpsTransformer(ABC, BaseEstimator, TransformerMixin): def __init__( self, parallel: Union[bool, int] = False, start_method: str = None, safe_inference_mode: bool = False, - dtype: np.dtype = np.int8, ): self.parallel = parallel self.start_method = start_method self.safe_inference_mode = safe_inference_mode - self.dtype = dtype @property def nBits(self): @@ -98,34 +96,25 @@ def get_feature_names_out(self, input_features=None): prefix = self._get_column_prefix() return np.array([f"{prefix}_{i}" for i in range(1, self.fpSize + 1)]) - @abstractmethod - def _mol2fp(self, mol): - """Generate fingerprint from mol - - MUST BE OVERWRITTEN - """ - raise NotImplementedError("_mol2fp not implemented") - - def _fp2array(self, fp): - if fp: - arr = np.zeros((self.fpSize,), dtype=self.dtype) - DataStructs.ConvertToNumpyArray(fp, arr) - return arr - else: - return np.ma.masked_all((self.fpSize,), dtype=self.dtype) - - def _transform_mol(self, mol): + def _safe_transform_mol(self, mol): + """Handle safe inference mode with masked arrays""" if not mol and self.safe_inference_mode: - return self._fp2array(False) + return np.ma.masked_all(self.fpSize) + try: - fp = self._mol2fp(mol) - return self._fp2array(fp) + result = self._transform_mol(mol) + return result except Exception as e: if self.safe_inference_mode: - return self._fp2array(False) + return np.ma.masked_all(self.fpSize) else: raise e + @abstractmethod + def _transform_mol(self, mol): + """Transform a single molecule to numpy array""" + raise NotImplementedError + def fit(self, X, y=None): """Included for scikit-learn compatibility @@ -137,15 +126,20 @@ def fit(self, X, y=None): def _transform(self, X): if self.safe_inference_mode: # Use the new method with masked arrays if we're in safe inference mode - arrays = [self._transform_mol(mol) for mol in X] + arrays = [self._safe_transform_mol(mol) for mol in X] print(arrays) return np.ma.stack(arrays) - else: + elif hasattr( + self, "dtype" + ): # TODO, it seems a bit of a code smell that we have to preemptively test a property from the baseclass? # Use the original, faster method if we're not in safe inference mode arr = np.zeros((len(X), self.fpSize), dtype=self.dtype) for i, mol in enumerate(X): arr[i, :] = self._transform_mol(mol) return arr + else: # We are unsure on the dtype, so we don't use a preassigned array #TODO test time differnece to previous + arrays = [self._transform_mol(mol) for mol in X] + return np.stack(arrays) def _transform_sparse(self, X): arr = np.zeros((len(X), self.fpSize), dtype=self.dtype) @@ -202,20 +196,49 @@ def transform(self, X, y=None): return arr -class FpsGeneratorTransformer(FpsTransformer): - _regenerate_on_properties = () +class FpsTransformer(BaseFpsTransformer): + """Classic fingerprint transformer using mol2fp pattern""" - def _fp2array(self, fp): - raise DeprecationWarning("Generators can directly return fingerprints") + def __init__( + self, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + dtype: np.dtype = np.int8, + ): + super().__init__(parallel=parallel, safe_inference_mode=safe_inference_mode) + self.dtype = dtype + def _transform_mol(self, mol): + """Implements the mol -> rdkit fingerprint data structure -> numpy array pattern""" + fp = self._mol2fp(mol) + return self._fp2array(fp) + + @abstractmethod def _mol2fp(self, mol): - raise DeprecationWarning("use _mol2array") + """Generate fingerprint from mol + + MUST BE OVERWRITTEN + """ + raise NotImplementedError("_mol2fp not implemented") + + def _fp2array(self, fp): + """Convert RDKit fingerprint data structure to numpy array""" + if fp: + arr = np.zeros((self.fpSize,), dtype=self.dtype) + DataStructs.ConvertToNumpyArray(fp, arr) + return arr + else: + return np.ma.masked_all((self.fpSize,), dtype=self.dtype) + + +class FpsGeneratorTransformer(BaseFpsTransformer): + _regenerate_on_properties = () def __getstate__(self): # Get the state of the parent class state = super().__getstate__() state.update(self.get_params()) - # Remove the unpicklable property from the state + # Remove the potentiallyunpicklable property from the state state.pop("_fpgen", None) # fpgen is not picklable return state @@ -234,6 +257,8 @@ def __setstate__(self, state): ] self._generate_fp_generator() + # TODO: overload set_params in order to not make multiple calls to _generate_fp_generator + def __setattr__(self, name: str, value): super().__setattr__(name, value) if ( diff --git a/tests/test_fptransformersgenerator.py b/tests/test_fptransformersgenerator.py index 81da19c..c61f6e9 100644 --- a/tests/test_fptransformersgenerator.py +++ b/tests/test_fptransformersgenerator.py @@ -2,25 +2,38 @@ import tempfile import pytest import numpy as np -from fixtures import mols_list, smiles_list, mols_container, smiles_container, fingerprint, chiral_smiles_list, chiral_mols_list +from fixtures import ( + mols_list, + smiles_list, + mols_container, + smiles_container, + fingerprint, + chiral_smiles_list, + chiral_mols_list, +) from sklearn import clone -from scikit_mol.fingerprints import (MorganFPGeneratorTransformer, - RDKitFPGeneratorTransformer, - AtomPairFPGeneratorTransformer, - TopologicalTorsionFPGeneatorTransformer, - ) +from scikit_mol.fingerprints import ( + MorganFPGeneratorTransformer, + RDKitFPGeneratorTransformer, + AtomPairFPGeneratorTransformer, + TopologicalTorsionFPGeneatorTransformer, +) -test_transformers = [MorganFPGeneratorTransformer, RDKitFPGeneratorTransformer, - AtomPairFPGeneratorTransformer, TopologicalTorsionFPGeneatorTransformer] +test_transformers = [ + MorganFPGeneratorTransformer, + RDKitFPGeneratorTransformer, + AtomPairFPGeneratorTransformer, + TopologicalTorsionFPGeneatorTransformer, +] -@pytest.mark.parametrize("transformer_class", test_transformers) -def test_fpstransformer_fp2array(transformer_class, fingerprint): - transformer = transformer_class() +# @pytest.mark.parametrize("transformer_class", test_transformers) +# def test_fpstransformer_fp2array(transformer_class, fingerprint): +# transformer = transformer_class() - with pytest.raises(DeprecationWarning, match='Generators can directly return fingerprints'): - fp = transformer._fp2array(fingerprint) +# with pytest.raises(DeprecationWarning, match='Generators can directly return fingerprints'): +# fp = transformer._fp2array(fingerprint) @pytest.mark.parametrize("transformer_class", test_transformers) @@ -28,75 +41,79 @@ def test_fpstransformer_transform_mol(transformer_class, mols_list): transformer = transformer_class() fp = transformer._transform_mol(mols_list[0]) - #See that fp is the correct type, shape and bit count - assert(type(fp) == type(np.array([0]))) - assert(fp.shape == (2048,)) + # See that fp is the correct type, shape and bit count + assert type(fp) == type(np.array([0])) + assert fp.shape == (2048,) if isinstance(transformer, RDKitFPGeneratorTransformer): - assert(fp.sum() == 104) + assert fp.sum() == 104 elif isinstance(transformer, AtomPairFPGeneratorTransformer): - assert (fp.sum() == 32) + assert fp.sum() == 32 elif isinstance(transformer, TopologicalTorsionFPGeneatorTransformer): - assert (fp.sum() == 12) + assert fp.sum() == 12 elif isinstance(transformer, MorganFPGeneratorTransformer): - assert (fp.sum() == 14) + assert fp.sum() == 14 else: raise NotImplementedError("missing Assert") + @pytest.mark.parametrize("transformer_class", test_transformers) def test_clonability(transformer_class): transformer = transformer_class() - params = transformer.get_params() + params = transformer.get_params() t2 = clone(transformer) params_2 = t2.get_params() - #Parameters of cloned transformers should be the same - assert all([ params[key] == params_2[key] for key in params.keys()]) - #Cloned transformers should not be the same object + # Parameters of cloned transformers should be the same + assert all([params[key] == params_2[key] for key in params.keys()]) + # Cloned transformers should not be the same object assert t2 != transformer + @pytest.mark.parametrize("transformer_class", test_transformers) def test_set_params(transformer_class): transformer = transformer_class() - params = transformer.get_params() - #change extracted dictionary - params['fpSize'] = 4242 - #change params in transformer - transformer.set_params(fpSize = 4242) + params = transformer.get_params() + # change extracted dictionary + params["fpSize"] = 4242 + # change params in transformer + transformer.set_params(fpSize=4242) # get parameters as dictionary and assert that it is the same params_2 = transformer.get_params() - assert all([ params[key] == params_2[key] for key in params.keys()]) + assert all([params[key] == params_2[key] for key in params.keys()]) + @pytest.mark.parametrize("transformer_class", test_transformers) def test_transform(mols_container, transformer_class): transformer = transformer_class() - #Test the different transformers - params = transformer.get_params() + # Test the different transformers + params = transformer.get_params() fps = transformer.transform(mols_container) - #Assert that the same length of input and output + # Assert that the same length of input and output assert len(fps) == len(mols_container) - fpsize = params['fpSize'] + fpsize = params["fpSize"] assert len(fps[0]) == fpsize + @pytest.mark.parametrize("transformer_class", test_transformers) def test_transform_parallel(mols_container, transformer_class): transformer = transformer_class() - #Test the different transformers + # Test the different transformers transformer.set_params(parallel=True) - params = transformer.get_params() + params = transformer.get_params() fps = transformer.transform(mols_container) - #Assert that the same length of input and output + # Assert that the same length of input and output assert len(fps) == len(mols_container) - fpsize = params['fpSize'] + fpsize = params["fpSize"] assert len(fps[0]) == fpsize @pytest.mark.parametrize("transformer_class", test_transformers) def test_picklable(transformer_class): - #Test the different transformers + # Test the different transformers transformer = transformer_class() p = transformer.get_params() @@ -107,8 +124,8 @@ def test_picklable(transformer_class): print(p) print(vars(transformer)) print(vars(t2)) - assert(transformer.get_params() == t2.get_params()) - + assert transformer.get_params() == t2.get_params() + @pytest.mark.parametrize("transfomer", test_transformers) def assert_transformer_set_params(transfomer, new_params, mols_list): @@ -128,20 +145,36 @@ def assert_transformer_set_params(transfomer, new_params, mols_list): # Now fp_default should not be the same as fp_reset_params - assert ~np.all([np.array_equal(fp_default, fp_reset_params) for fp_default, fp_reset_params in zip(fps_default, fps_reset_params)]), f"Assertation error, FP appears the same, although the {key} should be changed from {default_params[key]} to {params[key]}" + assert ~np.all( + [ + np.array_equal(fp_default, fp_reset_params) + for fp_default, fp_reset_params in zip(fps_default, fps_reset_params) + ] + ), f"Assertation error, FP appears the same, although the {key} should be changed from {default_params[key]} to {params[key]}" # fp_reset_params and fp_init_new_params should however be the same - assert np.all([np.array_equal(fp_init_new_params, fp_reset_params) for fp_init_new_params, fp_reset_params in zip(fps_init_new_params, fps_reset_params)]) , f"Assertation error, FP appears to be different, although the {key} should be changed back as well as initialized to {params[key]}" + assert np.all( + [ + np.array_equal(fp_init_new_params, fp_reset_params) + for fp_init_new_params, fp_reset_params in zip( + fps_init_new_params, fps_reset_params + ) + ] + ), f"Assertation error, FP appears to be different, although the {key} should be changed back as well as initialized to {params[key]}" def test_morgan_set_params(chiral_mols_list): - new_params = {'fpSize': 1024, - 'radius': 1, - 'useBondTypes': False,# TODO, why doesn't this change the FP? - 'useChirality': True, - 'useCounts': True, - 'useFeatures': True} - - assert_transformer_set_params(MorganFPGeneratorTransformer, new_params, chiral_mols_list) + new_params = { + "fpSize": 1024, + "radius": 1, + "useBondTypes": False, # TODO, why doesn't this change the FP? + "useChirality": True, + "useCounts": True, + "useFeatures": True, + } + + assert_transformer_set_params( + MorganFPGeneratorTransformer, new_params, chiral_mols_list + ) def test_atompairs_set_params(chiral_mols_list): @@ -150,39 +183,48 @@ def test_atompairs_set_params(chiral_mols_list): #'confId': -1, #'fromAtoms': 1, #'ignoreAtoms': 0, - 'includeChirality': True, - 'maxLength': 3, - 'minLength': 3, - 'fpSize': 1024, + "includeChirality": True, + "maxLength": 3, + "minLength": 3, + "fpSize": 1024, #'nBitsPerEntry': 3, #Todo: not setable with the generators? #'use2D': True, #TODO, understand why this can't be set different - 'useCounts': True} - - assert_transformer_set_params(AtomPairFPGeneratorTransformer, new_params, chiral_mols_list) + "useCounts": True, + } + + assert_transformer_set_params( + AtomPairFPGeneratorTransformer, new_params, chiral_mols_list + ) def test_topologicaltorsion_set_params(chiral_mols_list): - new_params = {#'atomInvariants': 0, - #'fromAtoms': 0, - #'ignoreAtoms': 0, - #'includeChirality': True, #TODO, figure out why this setting seems to give same FP wheter toggled or not - 'fpSize': 1024, - #'nBitsPerEntry': 3, #Todo: not setable with the generators? - 'targetSize': 5, - 'useCounts': True} - - assert_transformer_set_params(TopologicalTorsionFPGeneatorTransformer, new_params, chiral_mols_list) + new_params = { #'atomInvariants': 0, + #'fromAtoms': 0, + #'ignoreAtoms': 0, + #'includeChirality': True, #TODO, figure out why this setting seems to give same FP wheter toggled or not + "fpSize": 1024, + #'nBitsPerEntry': 3, #Todo: not setable with the generators? + "targetSize": 5, + "useCounts": True, + } + + assert_transformer_set_params( + TopologicalTorsionFPGeneatorTransformer, new_params, chiral_mols_list + ) + def test_RDKitFPTransformer(chiral_mols_list): - new_params = {#'atomInvariantsGenerator': None, - #'branchedPaths': False, - #'countBounds': 0, #TODO: What does this do? - 'countSimulation': True, - 'fpSize': 1024, - 'maxPath': 3, - 'minPath': 2, - 'numBitsPerFeature': 3, - 'useBondOrder': False, #TODO, why doesn't this change the FP? - #'useHs': False, #TODO, why doesn't this change the FP? - } - assert_transformer_set_params(RDKitFPGeneratorTransformer, new_params, chiral_mols_list) + new_params = { #'atomInvariantsGenerator': None, + #'branchedPaths': False, + #'countBounds': 0, #TODO: What does this do? + "countSimulation": True, + "fpSize": 1024, + "maxPath": 3, + "minPath": 2, + "numBitsPerFeature": 3, + "useBondOrder": False, # TODO, why doesn't this change the FP? + #'useHs': False, #TODO, why doesn't this change the FP? + } + assert_transformer_set_params( + RDKitFPGeneratorTransformer, new_params, chiral_mols_list + ) From ff8cf2eea6199d60ec4bee4e893d114d8d7add8c Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Fri, 22 Nov 2024 16:43:11 +0100 Subject: [PATCH 13/24] Updated child classes to honor the safe_inference_mode --- scikit_mol/fingerprints/atompair.py | 4 ++-- scikit_mol/fingerprints/baseclasses.py | 1 - scikit_mol/fingerprints/minhash.py | 2 ++ scikit_mol/fingerprints/morgan.py | 7 ++++++- scikit_mol/fingerprints/rdkitfp.py | 3 ++- scikit_mol/fingerprints/topologicaltorsion.py | 3 ++- 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/scikit_mol/fingerprints/atompair.py b/scikit_mol/fingerprints/atompair.py index aff8f9f..2198afd 100644 --- a/scikit_mol/fingerprints/atompair.py +++ b/scikit_mol/fingerprints/atompair.py @@ -100,9 +100,10 @@ def __init__( fpSize: int = 2048, useCounts: bool = False, parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, ): self._initializing = True - super().__init__(parallel=parallel) + super().__init__(parallel=parallel, safe_inference_mode=safe_inference_mode) self.fpSize = fpSize self.use2D = use2D self.includeChirality = includeChirality @@ -114,7 +115,6 @@ def __init__( self.fromAtoms = fromAtoms self.ignoreAtoms = ignoreAtoms self.atomInvariants = atomInvariants - self._generate_fp_generator() delattr(self, "_initializing") diff --git a/scikit_mol/fingerprints/baseclasses.py b/scikit_mol/fingerprints/baseclasses.py index 03ca11b..d1eef40 100644 --- a/scikit_mol/fingerprints/baseclasses.py +++ b/scikit_mol/fingerprints/baseclasses.py @@ -127,7 +127,6 @@ def _transform(self, X): if self.safe_inference_mode: # Use the new method with masked arrays if we're in safe inference mode arrays = [self._safe_transform_mol(mol) for mol in X] - print(arrays) return np.ma.stack(arrays) elif hasattr( self, "dtype" diff --git a/scikit_mol/fingerprints/minhash.py b/scikit_mol/fingerprints/minhash.py index 1c7e62a..9d0ec31 100644 --- a/scikit_mol/fingerprints/minhash.py +++ b/scikit_mol/fingerprints/minhash.py @@ -9,6 +9,7 @@ from rdkit.Chem import rdMHFPFingerprint +# TODO move to use FpsGeneratorTransformer class MHFingerprintTransformer(FpsTransformer): def __init__( self, @@ -105,6 +106,7 @@ def n_permutations(self, n_permutations): self._recreate_encoder() +# TODO use FpsGeneratorTransformer instead class SECFingerprintTransformer(FpsTransformer): # https://jcheminf.biomedcentral.com/articles/10.1186/s13321-018-0321-8 def __init__( diff --git a/scikit_mol/fingerprints/morgan.py b/scikit_mol/fingerprints/morgan.py index 37d7cf8..f7d6067 100644 --- a/scikit_mol/fingerprints/morgan.py +++ b/scikit_mol/fingerprints/morgan.py @@ -98,6 +98,7 @@ def __init__( useFeatures=False, useCounts=False, parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, ): """Transform RDKit mols into Count or bit-based hashed MorganFingerprints @@ -115,10 +116,14 @@ def __init__( use chemical features, rather than atom-type in calculation of the fingerprint keys, by default False useCounts : bool, optional If toggled will create the count and not bit-based fingerprint, by default False + parallel : bool or int, optional + If True, will use all available cores, if int will use that many cores, by default False + safe_inference_mode : bool, optional + If True, will return masked arrays for invalid mols, by default False """ self._initializing = True - super().__init__(parallel=parallel) + super().__init__(parallel=parallel, safe_inference_mode=safe_inference_mode) self.fpSize = fpSize self.radius = radius self.useChirality = useChirality diff --git a/scikit_mol/fingerprints/rdkitfp.py b/scikit_mol/fingerprints/rdkitfp.py index 28ce0a8..ad87a26 100644 --- a/scikit_mol/fingerprints/rdkitfp.py +++ b/scikit_mol/fingerprints/rdkitfp.py @@ -114,6 +114,7 @@ def __init__( numBitsPerFeature: int = 2, useCounts: bool = False, parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, ): """Calculates the RDKit fingerprints @@ -139,7 +140,7 @@ def __init__( the number of bits set per path/subgraph found, by default 2 """ self._initializing = True - super().__init__(parallel=parallel) + super().__init__(parallel=parallel, safe_inference_mode=safe_inference_mode) self.minPath = minPath self.maxPath = maxPath self.useHs = useHs diff --git a/scikit_mol/fingerprints/topologicaltorsion.py b/scikit_mol/fingerprints/topologicaltorsion.py index 0b6640d..63b68bf 100644 --- a/scikit_mol/fingerprints/topologicaltorsion.py +++ b/scikit_mol/fingerprints/topologicaltorsion.py @@ -80,9 +80,10 @@ def __init__( fpSize: int = 2048, useCounts: bool = False, parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, ): self._initializing = True - super().__init__(parallel=parallel) + super().__init__(parallel=parallel, safe_inference_mode=safe_inference_mode) self.fpSize = fpSize self.includeChirality = includeChirality self.targetSize = targetSize From 7d9941ad1630176ff884c31c67c57bfd5ac477cd Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Fri, 22 Nov 2024 20:45:14 +0100 Subject: [PATCH 14/24] working prototype for the soft transition to generator classes with proper deprecation warnings. Only implemented in the MorganFP so far --- scikit_mol/fingerprints/baseclasses.py | 57 ++++++++++++++++++++++---- scikit_mol/fingerprints/morgan.py | 4 ++ 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/scikit_mol/fingerprints/baseclasses.py b/scikit_mol/fingerprints/baseclasses.py index d1eef40..5ce7f00 100644 --- a/scikit_mol/fingerprints/baseclasses.py +++ b/scikit_mol/fingerprints/baseclasses.py @@ -2,7 +2,8 @@ import multiprocessing import re import inspect -from warnings import warn +from warnings import warn, simplefilter + from typing import Union from rdkit import DataStructs @@ -30,6 +31,7 @@ from abc import ABC, abstractmethod +simplefilter("always", DeprecationWarning) _PATTERN_FINGERPRINT_TRANSFORMER = re.compile( r"^(?P\w+)FingerprintTransformer$" @@ -47,21 +49,26 @@ def __init__( self.start_method = start_method self.safe_inference_mode = safe_inference_mode + # TODO, remove when finally deprecating nBits and dtype @property def nBits(self): warn( - "nBits will be replace by fpSize, due to changes harmonization!", + "nBits will be replaced by fpSize, due to changes harmonization!", DeprecationWarning, + stacklevel=2, ) return self.fpSize + # TODO, remove when finally deprecating nBits and dtype @nBits.setter def nBits(self, nBits): - warn( - "nBits will be replace by fpSize, due to changes harmonization!", - DeprecationWarning, - ) - self.fpSize = nBits + if nBits is not None: + warn( + "nBits will be replaced by fpSize, due to changes harmonization!", + DeprecationWarning, + stacklevel=3, + ) + self.fpSize = nBits def _get_column_prefix(self) -> str: matched = _PATTERN_FINGERPRINT_TRANSFORMER.match(type(self).__name__) @@ -229,6 +236,13 @@ def _fp2array(self, fp): else: return np.ma.masked_all((self.fpSize,), dtype=self.dtype) + # TODO, remove when finally deprecating nBits + def _get_param_names(self): + """Get parameter names excluding deprecated parameters""" + params = super()._get_param_names() + # Remove deprecated parameters before they're accessed + return [p for p in params if p not in ("nBits")] + class FpsGeneratorTransformer(BaseFpsTransformer): _regenerate_on_properties = () @@ -278,6 +292,35 @@ def _transform_mol(self, mol) -> np.array: """ raise NotImplementedError("_transform_mol not implemented") + # TODO, remove when finally deprecating nBits and dtype + @property + def dtype(self): + warn( + "dtype is no longer supported, due to move to generator based fingerprints", + DeprecationWarning, + stacklevel=2, + ) + return None + + # TODO, remove when finally deprecating nBits and dtype + @dtype.setter + def dtype(self, dtype): + if dtype is not None: + print("Tester") + warn( + "dtype is no longer supported, due to move to generator based fingerprints", + DeprecationWarning, + stacklevel=3, + ) + pass + + # TODO, remove when finally deprecating nBits and dtype + def _get_param_names(self): + """Get parameter names excluding deprecated parameters""" + params = super()._get_param_names() + # Remove deprecated parameters before they're accessed + return [p for p in params if p not in ("dtype", "nBits")] + def parallel_helper(args): """Parallel_helper takes a tuple with classname, the objects parameters and the mols to process. diff --git a/scikit_mol/fingerprints/morgan.py b/scikit_mol/fingerprints/morgan.py index f7d6067..8b7292f 100644 --- a/scikit_mol/fingerprints/morgan.py +++ b/scikit_mol/fingerprints/morgan.py @@ -99,6 +99,8 @@ def __init__( useCounts=False, parallel: Union[bool, int] = False, safe_inference_mode: bool = False, + dtype: np.dtype = None, + nBits: int = None, ): """Transform RDKit mols into Count or bit-based hashed MorganFingerprints @@ -130,6 +132,8 @@ def __init__( self.useFeatures = useFeatures self.useCounts = useCounts self.useBondTypes = useBondTypes + self.dtype = dtype + self.nBits = nBits self._generate_fp_generator() delattr(self, "_initializing") From d425b72968769f0a76905b62fe1fa8df27fb9a01 Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Sun, 24 Nov 2024 08:11:19 +0100 Subject: [PATCH 15/24] Minor fixes in baseclasses --- scikit_mol/fingerprints/baseclasses.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scikit_mol/fingerprints/baseclasses.py b/scikit_mol/fingerprints/baseclasses.py index 5ce7f00..8664630 100644 --- a/scikit_mol/fingerprints/baseclasses.py +++ b/scikit_mol/fingerprints/baseclasses.py @@ -245,6 +245,8 @@ def _get_param_names(self): class FpsGeneratorTransformer(BaseFpsTransformer): + """Abstract base class for fingerprint transformers based on (unpicklable)fingerprint generators""" + _regenerate_on_properties = () def __getstate__(self): @@ -286,7 +288,7 @@ def _generate_fp_generator(self): @abstractmethod def _transform_mol(self, mol) -> np.array: - """Generate numpy array descriptor from mol + """Generate numpy array descriptor from RDKit molecule MUST BE OVERWRITTEN """ @@ -306,7 +308,6 @@ def dtype(self): @dtype.setter def dtype(self, dtype): if dtype is not None: - print("Tester") warn( "dtype is no longer supported, due to move to generator based fingerprints", DeprecationWarning, From f73558f5295fc58b726d9105bb5d8290e701497a Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Sun, 24 Nov 2024 08:16:52 +0100 Subject: [PATCH 16/24] Fixed exotic types --- scikit_mol/fingerprints/morgan.py | 89 +++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 5 deletions(-) diff --git a/scikit_mol/fingerprints/morgan.py b/scikit_mol/fingerprints/morgan.py index 8b7292f..6ddb6b9 100644 --- a/scikit_mol/fingerprints/morgan.py +++ b/scikit_mol/fingerprints/morgan.py @@ -14,7 +14,7 @@ from .baseclasses import FpsTransformer, FpsGeneratorTransformer -class MorganFingerprintTransformer(FpsTransformer): +class MorganFingerprintTransformerClassic(FpsTransformer): def __init__( self, fpSize=2048, @@ -80,6 +80,85 @@ def _mol2fp(self, mol): ) +class MorganFingerprintTransformer(FpsGeneratorTransformer): + _regenerate_on_properties = ( + "radius", + "fpSize", + "useChirality", + "useFeatures", + "useBondTypes", + ) + + def __init__( + self, + fpSize=2048, + radius=2, + useChirality=False, + useBondTypes=True, + useFeatures=False, + useCounts=False, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + dtype: np.dtype = None, + nBits: int = None, + ): + """Transform RDKit mols into Count or bit-based hashed MorganFingerprints + + Parameters + ---------- + fpsize : int, optional + Size of the hashed fingerprint, by default 2048 + radius : int, optional + Radius of the fingerprint, by default 2 + useChirality : bool, optional + Include chirality in calculation of the fingerprint keys, by default False + useBondTypes : bool, optional + Include bondtypes in calculation of the fingerprint keys, by default True + useFeatures : bool, optional + use chemical features, rather than atom-type in calculation of the fingerprint keys, by default False + useCounts : bool, optional + If toggled will create the count and not bit-based fingerprint, by default False + parallel : bool or int, optional + If True, will use all available cores, if int will use that many cores, by default False + safe_inference_mode : bool, optional + If True, will return masked arrays for invalid mols, by default False + """ + + self._initializing = True + super().__init__(parallel=parallel, safe_inference_mode=safe_inference_mode) + self.fpSize = fpSize + self.radius = radius + self.useChirality = useChirality + self.useFeatures = useFeatures + self.useCounts = useCounts + self.useBondTypes = useBondTypes + self.dtype = dtype + self.nBits = nBits + + self._generate_fp_generator() + delattr(self, "_initializing") + + def _generate_fp_generator(self): + if self.useFeatures: + atomInvariantsGenerator = GetMorganFeatureAtomInvGen() + else: + atomInvariantsGenerator = None + + self._fpgen = GetMorganGenerator( + radius=int(self.radius), + fpSize=int(self.fpSize), + includeChirality=bool(self.useChirality), + useBondTypes=bool(self.useBondTypes), + atomInvariantsGenerator=atomInvariantsGenerator, + ) + + def _transform_mol(self, mol) -> np.array: + if self.useCounts: + return self._fpgen.GetCountFingerprintAsNumPy(mol) + else: + return self._fpgen.GetFingerprintAsNumPy(mol) + + class MorganFPGeneratorTransformer(FpsGeneratorTransformer): _regenerate_on_properties = ( "radius", @@ -145,10 +224,10 @@ def _generate_fp_generator(self): atomInvariantsGenerator = None self._fpgen = GetMorganGenerator( - radius=self.radius, - fpSize=self.fpSize, - includeChirality=self.useChirality, - useBondTypes=self.useBondTypes, + radius=int(self.radius), + fpSize=int(self.fpSize), + includeChirality=bool(self.useChirality), + useBondTypes=bool(self.useBondTypes), atomInvariantsGenerator=atomInvariantsGenerator, ) From 96b5b4211e79d399ea027e9b462c61b5f76584b8 Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Sun, 24 Nov 2024 08:23:06 +0100 Subject: [PATCH 17/24] Fixed test to use new baseclass --- tests/test_transformers.py | 227 +++++++++++++++++++++++++------------ 1 file changed, 156 insertions(+), 71 deletions(-) diff --git a/tests/test_transformers.py b/tests/test_transformers.py index b96d421..9352441 100644 --- a/tests/test_transformers.py +++ b/tests/test_transformers.py @@ -15,14 +15,33 @@ from sklearn.ensemble import RandomForestRegressor from scikit_mol.conversions import SmilesToMolTransformer from scikit_mol.core import SKLEARN_VERSION_PANDAS_OUT -from scikit_mol.fingerprints import (FpsTransformer, MACCSKeysFingerprintTransformer, RDKitFingerprintTransformer, AtomPairFingerprintTransformer, - TopologicalTorsionFingerprintTransformer, MorganFingerprintTransformer, SECFingerprintTransformer, - MHFingerprintTransformer, AvalonFingerprintTransformer, MorganFPGeneratorTransformer, - RDKitFPGeneratorTransformer, AtomPairFPGeneratorTransformer, TopologicalTorsionFPGeneatorTransformer) +from scikit_mol.fingerprints import ( + MACCSKeysFingerprintTransformer, + RDKitFingerprintTransformer, + AtomPairFingerprintTransformer, + TopologicalTorsionFingerprintTransformer, + MorganFingerprintTransformer, + SECFingerprintTransformer, + MHFingerprintTransformer, + AvalonFingerprintTransformer, + MorganFPGeneratorTransformer, + RDKitFPGeneratorTransformer, + AtomPairFPGeneratorTransformer, + TopologicalTorsionFPGeneatorTransformer, +) +from scikit_mol.fingerprints.baseclasses import BaseFpsTransformer from scikit_mol.descriptors import MolecularDescriptorTransformer -from fixtures import SLC6A4_subset, SLC6A4_subset_with_cddd, skip_pandas_output_test, mols_container, featurizer, combined_transformer +from fixtures import ( + SLC6A4_subset, + SLC6A4_subset_with_cddd, + skip_pandas_output_test, + mols_container, + featurizer, + combined_transformer, +) + def test_transformer(SLC6A4_subset): # load some toy data for quick testing on a small number of samples @@ -31,45 +50,83 @@ def test_transformer(SLC6A4_subset): X_train, X_test = X_smiles[:128], X_smiles[128:] Y_train, Y_test = Y[:128], Y[128:] - MorganFPGeneratorTransformer, - RDKitFPGeneratorTransformer, AtomPairFPGeneratorTransformer, TopologicalTorsionFPGeneatorTransformer + (MorganFPGeneratorTransformer,) + ( + RDKitFPGeneratorTransformer, + AtomPairFPGeneratorTransformer, + TopologicalTorsionFPGeneatorTransformer, + ) # run FP with default parameters except when useCounts can be given as an argument - FP_dict = {"MACCSTransformer": [MACCSKeysFingerprintTransformer, None], - "RDKitFPTransformer": [RDKitFingerprintTransformer, None], - "AtomPairFingerprintTransformer": [AtomPairFingerprintTransformer, False], - "AtomPairFingerprintTransformer useCounts": [AtomPairFingerprintTransformer, True], - "TopologicalTorsionFingerprintTransformer": [TopologicalTorsionFingerprintTransformer, False], - "TopologicalTorsionFingerprintTransformer useCounts": [TopologicalTorsionFingerprintTransformer, True], - "MorganTransformer": [MorganFingerprintTransformer, False], - "MorganTransformer useCounts": [MorganFingerprintTransformer, True], - "SECFingerprintTransformer": [SECFingerprintTransformer, None], - "MHFingerprintTransformer": [MHFingerprintTransformer, None], - 'AvalonFingerprintTransformer': [AvalonFingerprintTransformer, None], - 'MorganFPGeneratorTransformer': [MorganFPGeneratorTransformer, True], - 'MorganFPGeneratorTransformer': [MorganFPGeneratorTransformer, False], - 'RDKitFPGeneratorTransformer': [RDKitFPGeneratorTransformer, None], - 'AtomPairFPGeneratorTransformer': [AtomPairFPGeneratorTransformer, True], - 'AtomPairFPGeneratorTransformer': [ AtomPairFPGeneratorTransformer, False], - 'TopologicalTorsionFPGeneatorTransformer': [TopologicalTorsionFPGeneatorTransformer, True], - 'TopologicalTorsionFPGeneatorTransformer': [ TopologicalTorsionFPGeneatorTransformer, False], - } + FP_dict = { + "MACCSTransformer": [MACCSKeysFingerprintTransformer, None], + "RDKitFPTransformer": [RDKitFingerprintTransformer, None], + "AtomPairFingerprintTransformer": [AtomPairFingerprintTransformer, False], + "AtomPairFingerprintTransformer useCounts": [ + AtomPairFingerprintTransformer, + True, + ], + "TopologicalTorsionFingerprintTransformer": [ + TopologicalTorsionFingerprintTransformer, + False, + ], + "TopologicalTorsionFingerprintTransformer useCounts": [ + TopologicalTorsionFingerprintTransformer, + True, + ], + "MorganTransformer": [MorganFingerprintTransformer, False], + "MorganTransformer useCounts": [MorganFingerprintTransformer, True], + "SECFingerprintTransformer": [SECFingerprintTransformer, None], + "MHFingerprintTransformer": [MHFingerprintTransformer, None], + "AvalonFingerprintTransformer": [AvalonFingerprintTransformer, None], + "MorganFPGeneratorTransformer": [MorganFPGeneratorTransformer, True], + "MorganFPGeneratorTransformer": [MorganFPGeneratorTransformer, False], + "RDKitFPGeneratorTransformer": [RDKitFPGeneratorTransformer, None], + "AtomPairFPGeneratorTransformer": [AtomPairFPGeneratorTransformer, True], + "AtomPairFPGeneratorTransformer": [AtomPairFPGeneratorTransformer, False], + "TopologicalTorsionFPGeneatorTransformer": [ + TopologicalTorsionFPGeneatorTransformer, + True, + ], + "TopologicalTorsionFPGeneatorTransformer": [ + TopologicalTorsionFPGeneatorTransformer, + False, + ], + } # fit on toy data and print train/test score if successful or collect the failed FP failed_FP = [] for FP_name, (FP, useCounts) in FP_dict.items(): try: - print(f"\nrunning pipeline fitting and scoring for {FP_name} with useCounts={useCounts}") + print( + f"\nrunning pipeline fitting and scoring for {FP_name} with useCounts={useCounts}" + ) if useCounts is None: - pipeline = Pipeline([("s2m", SmilesToMolTransformer()), ("FP", FP()), ("RF", RandomForestRegressor())]) + pipeline = Pipeline( + [ + ("s2m", SmilesToMolTransformer()), + ("FP", FP()), + ("RF", RandomForestRegressor()), + ] + ) else: - pipeline = Pipeline([("s2m", SmilesToMolTransformer()), ("FP", FP(useCounts=useCounts)), ("RF", RandomForestRegressor())]) + pipeline = Pipeline( + [ + ("s2m", SmilesToMolTransformer()), + ("FP", FP(useCounts=useCounts)), + ("RF", RandomForestRegressor()), + ] + ) pipeline.fit(X_train, Y_train) train_score = pipeline.score(X_train, Y_train) test_score = pipeline.score(X_test, Y_test) - print(f"\nfitting and scoring completed train_score={train_score}, test_score={test_score}") + print( + f"\nfitting and scoring completed train_score={train_score}, test_score={test_score}" + ) except: - print(f"\n!!!! FAILED pipeline fitting and scoring for {FP_name} with useCounts={useCounts}") + print( + f"\n!!!! FAILED pipeline fitting and scoring for {FP_name} with useCounts={useCounts}" + ) failed_FP.append(FP_name) pass @@ -84,57 +141,81 @@ def test_transformer_pandas_output(SLC6A4_subset, pandas_output): X_smiles = X_smiles.to_frame() # run FP with default parameters except when useCounts can be given as an argument - FP_dict = {"MACCSTransformer": [MACCSKeysFingerprintTransformer, None], - "RDKitFPTransformer": [RDKitFingerprintTransformer, None], - "AtomPairFingerprintTransformer": [AtomPairFingerprintTransformer, False], - "AtomPairFingerprintTransformer useCounts": [AtomPairFingerprintTransformer, True], - "TopologicalTorsionFingerprintTransformer": [TopologicalTorsionFingerprintTransformer, False], - "TopologicalTorsionFingerprintTransformer useCounts": [TopologicalTorsionFingerprintTransformer, True], - "MorganTransformer": [MorganFingerprintTransformer, False], - "MorganTransformer useCounts": [MorganFingerprintTransformer, True], - "SECFingerprintTransformer": [SECFingerprintTransformer, None], - "MHFingerprintTransformer": [MHFingerprintTransformer, None], - 'AvalonFingerprintTransformer': [AvalonFingerprintTransformer, None], - 'MorganFPGeneratorTransformer': [MorganFPGeneratorTransformer, - True], - 'MorganFPGeneratorTransformer': [MorganFPGeneratorTransformer, - False], - 'RDKitFPGeneratorTransformer': [RDKitFPGeneratorTransformer, - None], - 'AtomPairFPGeneratorTransformer': [ - AtomPairFPGeneratorTransformer, True], - 'AtomPairFPGeneratorTransformer': [ - AtomPairFPGeneratorTransformer, False], - 'TopologicalTorsionFPGeneatorTransformer': [ - TopologicalTorsionFPGeneatorTransformer, True], - 'TopologicalTorsionFPGeneatorTransformer': [ - TopologicalTorsionFPGeneatorTransformer, False], - } + FP_dict = { + "MACCSTransformer": [MACCSKeysFingerprintTransformer, None], + "RDKitFPTransformer": [RDKitFingerprintTransformer, None], + "AtomPairFingerprintTransformer": [AtomPairFingerprintTransformer, False], + "AtomPairFingerprintTransformer useCounts": [ + AtomPairFingerprintTransformer, + True, + ], + "TopologicalTorsionFingerprintTransformer": [ + TopologicalTorsionFingerprintTransformer, + False, + ], + "TopologicalTorsionFingerprintTransformer useCounts": [ + TopologicalTorsionFingerprintTransformer, + True, + ], + "MorganTransformer": [MorganFingerprintTransformer, False], + "MorganTransformer useCounts": [MorganFingerprintTransformer, True], + "SECFingerprintTransformer": [SECFingerprintTransformer, None], + "MHFingerprintTransformer": [MHFingerprintTransformer, None], + "AvalonFingerprintTransformer": [AvalonFingerprintTransformer, None], + "MorganFPGeneratorTransformer": [MorganFPGeneratorTransformer, True], + "MorganFPGeneratorTransformer": [MorganFPGeneratorTransformer, False], + "RDKitFPGeneratorTransformer": [RDKitFPGeneratorTransformer, None], + "AtomPairFPGeneratorTransformer": [AtomPairFPGeneratorTransformer, True], + "AtomPairFPGeneratorTransformer": [AtomPairFPGeneratorTransformer, False], + "TopologicalTorsionFPGeneatorTransformer": [ + TopologicalTorsionFPGeneatorTransformer, + True, + ], + "TopologicalTorsionFPGeneatorTransformer": [ + TopologicalTorsionFPGeneatorTransformer, + False, + ], + } # fit on toy data and check that the output is a pandas dataframe failed_FP = [] for FP_name, (FP, useCounts) in FP_dict.items(): try: - print(f"\nrunning pipeline fitting and scoring for {FP_name} with useCounts={useCounts}") + print( + f"\nrunning pipeline fitting and scoring for {FP_name} with useCounts={useCounts}" + ) if useCounts is None: pipeline = Pipeline([("s2m", SmilesToMolTransformer()), ("FP", FP())]) else: - pipeline = Pipeline([("s2m", SmilesToMolTransformer()), ("FP", FP(useCounts=useCounts))]) + pipeline = Pipeline( + [("s2m", SmilesToMolTransformer()), ("FP", FP(useCounts=useCounts))] + ) pipeline.fit(X_smiles) X_transformed = pipeline.transform(X_smiles) - assert isinstance(X_transformed, pd.DataFrame), f"the output of {FP_name} is not a pandas dataframe" - assert X_transformed.shape[0] == len(X_smiles), f"the number of rows in the output of {FP_name} is not equal to the number of samples" - assert len(X_transformed.columns) == pipeline.named_steps["FP"].fpSize, f"the number of columns in the output of {FP_name} is not equal to the number of bits" + assert isinstance( + X_transformed, pd.DataFrame + ), f"the output of {FP_name} is not a pandas dataframe" + assert ( + X_transformed.shape[0] == len(X_smiles) + ), f"the number of rows in the output of {FP_name} is not equal to the number of samples" + assert ( + len(X_transformed.columns) == pipeline.named_steps["FP"].fpSize + ), f"the number of columns in the output of {FP_name} is not equal to the number of bits" print(f"\nfitting and transforming completed") except Exception as err: - print(f"\n!!!! FAILED pipeline fitting and transforming for {FP_name} with useCounts={useCounts}") + print( + f"\n!!!! FAILED pipeline fitting and transforming for {FP_name} with useCounts={useCounts}" + ) print("\n".join(err.args)) failed_FP.append(FP_name) pass # overall result - assert len(failed_FP) == 0, f"the following FP have failed pandas transformation {failed_FP}" + assert ( + len(failed_FP) == 0 + ), f"the following FP have failed pandas transformation {failed_FP}" + @skip_pandas_output_test def test_pandas_out_same_values(featurizer, mols_container): @@ -149,22 +230,29 @@ def test_pandas_out_same_values(featurizer, mols_container): assert result_default.shape == result_pandas.shape featurizer_class_with_nan = MolecularDescriptorTransformer if isinstance(featurizer, featurizer_class_with_nan): - assert (pd.isna(result_default) == pd.isna(result_pandas.values)).all(), "NaN values are not in the same positions in the default and pandas output" - nan_replacement = 0. + assert ( + pd.isna(result_default) == pd.isna(result_pandas.values) + ).all(), ( + "NaN values are not in the same positions in the default and pandas output" + ) + nan_replacement = 0.0 result_default = np.nan_to_num(result_default, nan=nan_replacement) result_pandas = result_pandas.fillna(nan_replacement) else: assert (result_default == result_pandas.values).all() + @skip_pandas_output_test -def test_combined_transformer_pandas_out(combined_transformer, SLC6A4_subset_with_cddd, pandas_output): +def test_combined_transformer_pandas_out( + combined_transformer, SLC6A4_subset_with_cddd, pandas_output +): result = combined_transformer.fit_transform(SLC6A4_subset_with_cddd) assert isinstance(result, pd.DataFrame) assert result.shape[0] == SLC6A4_subset_with_cddd.shape[0] n_cddd_features = SLC6A4_subset_with_cddd.columns.str.match(r"^cddd_\d+$").sum() pipeline_skmol = combined_transformer.named_transformers_["pipeline-1"] featurizer_skmol = pipeline_skmol[-1] - if isinstance(featurizer_skmol, FpsTransformer): + if isinstance(featurizer_skmol, BaseFpsTransformer): n_skmol_features = featurizer_skmol.fpSize elif isinstance(featurizer_skmol, MolecularDescriptorTransformer): n_skmol_features = len(featurizer_skmol.desc_list) @@ -172,6 +260,3 @@ def test_combined_transformer_pandas_out(combined_transformer, SLC6A4_subset_wit raise ValueError(f"Unexpected featurizer type {type(featurizer_skmol)}") expected_n_features = n_cddd_features + n_skmol_features assert result.shape[1] == expected_n_features - - - From b8346f63f08c91be10b5e88ff332b790d0ac3773 Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Sun, 24 Nov 2024 09:08:34 +0100 Subject: [PATCH 18/24] Fixed the tests to move from classic to generator for morgan. Not the most flexible testing setup tbh --- tests/fixtures.py | 5 +++ tests/test_fptransformers.py | 62 +++++++++++++-------------- tests/test_fptransformersgenerator.py | 6 ++- 3 files changed, 39 insertions(+), 34 deletions(-) diff --git a/tests/fixtures.py b/tests/fixtures.py index 2b5a2e6..c3f392c 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -197,3 +197,8 @@ def combined_transformer(featurizer): remainder="drop", ) return transformer + + +@pytest.fixture +def morgan_transformer(): + return MorganFingerprintTransformer() diff --git a/tests/test_fptransformers.py b/tests/test_fptransformers.py index 4ad1e9d..debf9ef 100644 --- a/tests/test_fptransformers.py +++ b/tests/test_fptransformers.py @@ -29,11 +29,6 @@ ) -@pytest.fixture -def morgan_transformer(): - return MorganFingerprintTransformer() - - @pytest.fixture def rdkit_transformer(): return RDKitFingerprintTransformer() @@ -69,25 +64,26 @@ def avalon_transformer(): return AvalonFingerprintTransformer() -def test_fpstransformer_fp2array(morgan_transformer, fingerprint): - fp = morgan_transformer._fp2array(fingerprint) - # See that fp is the correct type, shape and bit count - assert type(fp) == type(np.array([0])) - assert fp.shape == (1000,) - assert fp.sum() == 25 +# morgan is no longer a fptransformer but a generator transformer, but why was this the only one to be tested here? +# def test_fpstransformer_fp2array(morgan_transformer, fingerprint): +# fp = morgan_transformer._fp2array(fingerprint) +# # See that fp is the correct type, shape and bit count +# assert type(fp) == type(np.array([0])) +# assert fp.shape == (1000,) +# assert fp.sum() == 25 -def test_fpstransformer_transform_mol(morgan_transformer, mols_list): - fp = morgan_transformer._transform_mol(mols_list[0]) - # See that fp is the correct type, shape and bit count - assert type(fp) == type(np.array([0])) - assert fp.shape == (2048,) - assert fp.sum() == 14 +# def test_fpstransformer_transform_mol(morgan_transformer, mols_list): +# fp = morgan_transformer._transform_mol(mols_list[0]) +# # See that fp is the correct type, shape and bit count +# assert type(fp) == type(np.array([0])) +# assert fp.shape == (2048,) +# assert fp.sum() == 14 def test_clonability( maccs_transformer, - morgan_transformer, + # morgan_transformer, rdkit_transformer, atompair_transformer, topologicaltorsion_transformer, @@ -97,7 +93,7 @@ def test_clonability( ): for t in [ maccs_transformer, - morgan_transformer, + # morgan_transformer, rdkit_transformer, atompair_transformer, topologicaltorsion_transformer, @@ -115,7 +111,7 @@ def test_clonability( def test_set_params( - morgan_transformer, + # morgan_transformer, rdkit_transformer, atompair_transformer, topologicaltorsion_transformer, @@ -124,7 +120,7 @@ def test_set_params( avalon_transformer, ): for t in [ - morgan_transformer, + # morgan_transformer, atompair_transformer, topologicaltorsion_transformer, avalon_transformer, @@ -148,7 +144,7 @@ def test_set_params( def test_transform( mols_container, - morgan_transformer, + # morgan_transformer, rdkit_transformer, atompair_transformer, topologicaltorsion_transformer, @@ -159,7 +155,7 @@ def test_transform( ): # Test the different transformers for t in [ - morgan_transformer, + # morgan_transformer, atompair_transformer, topologicaltorsion_transformer, maccs_transformer, @@ -182,7 +178,7 @@ def test_transform( def test_transform_parallel( mols_container, - morgan_transformer, + # morgan_transformer, rdkit_transformer, atompair_transformer, topologicaltorsion_transformer, @@ -193,7 +189,7 @@ def test_transform_parallel( ): # Test the different transformers for t in [ - morgan_transformer, + # morgan_transformer, atompair_transformer, topologicaltorsion_transformer, maccs_transformer, @@ -215,7 +211,7 @@ def test_transform_parallel( def test_picklable( - morgan_transformer, + # morgan_transformer, rdkit_transformer, atompair_transformer, topologicaltorsion_transformer, @@ -225,7 +221,7 @@ def test_picklable( ): # Test the different transformers for t in [ - morgan_transformer, + # morgan_transformer, atompair_transformer, topologicaltorsion_transformer, maccs_transformer, @@ -386,7 +382,7 @@ def test_AvalonFingerprintTransformer(chiral_mols_list): def test_transform_with_safe_inference_mode( mols_with_invalid_container, - morgan_transformer, + # morgan_transformer, rdkit_transformer, atompair_transformer, topologicaltorsion_transformer, @@ -395,7 +391,7 @@ def test_transform_with_safe_inference_mode( avalon_transformer, ): for t in [ - morgan_transformer, + # morgan_transformer, atompair_transformer, topologicaltorsion_transformer, maccs_transformer, @@ -418,7 +414,7 @@ def test_transform_with_safe_inference_mode( def test_transform_without_safe_inference_mode( mols_with_invalid_container, - morgan_transformer, + # morgan_transformer, rdkit_transformer, atompair_transformer, topologicaltorsion_transformer, @@ -428,7 +424,7 @@ def test_transform_without_safe_inference_mode( # MHFP seem to accept invalid mols and return 0,0,0,0's ): for t in [ - morgan_transformer, + # morgan_transformer, atompair_transformer, topologicaltorsion_transformer, maccs_transformer, @@ -447,7 +443,7 @@ def test_transform_without_safe_inference_mode( # Add this test to check parallel processing with error handling def test_transform_parallel_with_safe_inference_mode( mols_with_invalid_container, - morgan_transformer, + # morgan_transformer, rdkit_transformer, atompair_transformer, topologicaltorsion_transformer, @@ -456,7 +452,7 @@ def test_transform_parallel_with_safe_inference_mode( avalon_transformer, ): for t in [ - morgan_transformer, + # morgan_transformer, atompair_transformer, topologicaltorsion_transformer, maccs_transformer, diff --git a/tests/test_fptransformersgenerator.py b/tests/test_fptransformersgenerator.py index c61f6e9..f35771b 100644 --- a/tests/test_fptransformersgenerator.py +++ b/tests/test_fptransformersgenerator.py @@ -15,12 +15,14 @@ from scikit_mol.fingerprints import ( MorganFPGeneratorTransformer, + MorganFingerprintTransformer, RDKitFPGeneratorTransformer, AtomPairFPGeneratorTransformer, TopologicalTorsionFPGeneatorTransformer, ) test_transformers = [ + MorganFingerprintTransformer, MorganFPGeneratorTransformer, RDKitFPGeneratorTransformer, AtomPairFPGeneratorTransformer, @@ -53,8 +55,10 @@ def test_fpstransformer_transform_mol(transformer_class, mols_list): assert fp.sum() == 12 elif isinstance(transformer, MorganFPGeneratorTransformer): assert fp.sum() == 14 + elif isinstance(transformer, MorganFingerprintTransformer): + assert fp.sum() == 14 else: - raise NotImplementedError("missing Assert") + raise NotImplementedError(f"missing Assert for {transformer_class}") @pytest.mark.parametrize("transformer_class", test_transformers) From d9b8eb711267c92e789efd119c543a911fc64533 Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Sun, 24 Nov 2024 09:11:48 +0100 Subject: [PATCH 19/24] Minor changes to tests --- scikit_mol/fingerprints/baseclasses.py | 1 + tests/test_parameter_types.py | 68 ++++++++++++++++---------- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/scikit_mol/fingerprints/baseclasses.py b/scikit_mol/fingerprints/baseclasses.py index 8664630..30bfc5c 100644 --- a/scikit_mol/fingerprints/baseclasses.py +++ b/scikit_mol/fingerprints/baseclasses.py @@ -139,6 +139,7 @@ def _transform(self, X): self, "dtype" ): # TODO, it seems a bit of a code smell that we have to preemptively test a property from the baseclass? # Use the original, faster method if we're not in safe inference mode + # This also triggers a deprecation warning! arr = np.zeros((len(X), self.fpSize), dtype=self.dtype) for i, mol in enumerate(X): arr[i, :] = self._transform_mol(mol) diff --git a/tests/test_parameter_types.py b/tests/test_parameter_types.py index f175c87..ceea00c 100644 --- a/tests/test_parameter_types.py +++ b/tests/test_parameter_types.py @@ -1,46 +1,60 @@ import pytest import numpy as np from rdkit import Chem -from fixtures import mols_list, smiles_list -from test_fptransformers import morgan_transformer, atompair_transformer, topologicaltorsion_transformer, rdkit_transformer, avalon_transformer - - -def test_Transformer_exotic_types(mols_list, morgan_transformer,atompair_transformer, topologicaltorsion_transformer, avalon_transformer): - for transformer in [morgan_transformer, atompair_transformer, topologicaltorsion_transformer, avalon_transformer]: +from fixtures import mols_list, smiles_list, morgan_transformer +from test_fptransformers import ( + atompair_transformer, + topologicaltorsion_transformer, + rdkit_transformer, + avalon_transformer, +) + + +def test_Transformer_exotic_types( + mols_list, + morgan_transformer, + atompair_transformer, + topologicaltorsion_transformer, + avalon_transformer, +): + for transformer in [ + morgan_transformer, + atompair_transformer, + topologicaltorsion_transformer, + avalon_transformer, + ]: params = transformer.get_params() for useCounts in [np.bool_(True), np.bool_(False)]: - for key, value in params.items(): if isinstance(value, int): exotic_type_value = np.int64(value) elif isinstance(value, bool): exotic_type_value = np.bool_(value) else: - print(f'{key}:{value}:{type(value)}') + print(f"{key}:{value}:{type(value)}") exotic_type_value = value - exotic_params = {key:exotic_type_value, 'useCounts':useCounts} - print(exotic_params) + exotic_params = {key: exotic_type_value, "useCounts": useCounts} + print(exotic_params) transformer.set_params(**exotic_params) transformer.transform(mols_list) def test_RDKFp_exotic_types(mols_list, rdkit_transformer): - transformer = rdkit_transformer - params = transformer.get_params() - - for key, value in params.items(): - if isinstance(value, int): - exotic_type_value = np.int64(value) - elif isinstance(value, bool): - exotic_type_value = np.bool_(value) - else: - print(f'{key}:{value}:{type(value)}') - exotic_type_value = value - - exotic_params = {key:exotic_type_value} - print(exotic_params) - transformer.set_params(**exotic_params) - transformer.transform(mols_list) - + transformer = rdkit_transformer + params = transformer.get_params() + + for key, value in params.items(): + if isinstance(value, int): + exotic_type_value = np.int64(value) + elif isinstance(value, bool): + exotic_type_value = np.bool_(value) + else: + print(f"{key}:{value}:{type(value)}") + exotic_type_value = value + + exotic_params = {key: exotic_type_value} + print(exotic_params) + transformer.set_params(**exotic_params) + transformer.transform(mols_list) From 646808fd1abef6615b8da04e64b761c53ffb179e Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Sun, 24 Nov 2024 09:40:09 +0100 Subject: [PATCH 20/24] Updated test to reflect change to generator subclasses --- scikit_mol/fingerprints/atompair.py | 68 ++++++- scikit_mol/fingerprints/rdkitfp.py | 90 ++++++++- scikit_mol/fingerprints/topologicaltorsion.py | 57 +++++- tests/fixtures.py | 15 ++ tests/test_fptransformers.py | 189 +++++------------- tests/test_fptransformersgenerator.py | 40 ++-- tests/test_parameter_types.py | 8 +- 7 files changed, 300 insertions(+), 167 deletions(-) diff --git a/scikit_mol/fingerprints/atompair.py b/scikit_mol/fingerprints/atompair.py index 2198afd..f468575 100644 --- a/scikit_mol/fingerprints/atompair.py +++ b/scikit_mol/fingerprints/atompair.py @@ -10,7 +10,7 @@ from rdkit.Chem import rdMolDescriptors -class AtomPairFingerprintTransformer(FpsTransformer): +class AtomPairFingerprintTransformerClassic(FpsTransformer): def __init__( self, minLength: int = 1, @@ -78,6 +78,72 @@ def _mol2fp(self, mol): ) +class AtomPairFingerprintTransformer(FpsGeneratorTransformer): + _regenerate_on_properties = ( + "fpSize", + "includeChirality", + "use2D", + "minLength", + "maxLength", + ) + + def __init__( + self, + minLength: int = 1, + maxLength: int = 30, + fromAtoms=None, + ignoreAtoms=None, + atomInvariants=None, + includeChirality: bool = False, + use2D: bool = True, + confId: int = -1, + fpSize: int = 2048, + useCounts: bool = False, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + ): + self._initializing = True + super().__init__(parallel=parallel, safe_inference_mode=safe_inference_mode) + self.fpSize = fpSize + self.use2D = use2D + self.includeChirality = includeChirality + self.minLength = minLength + self.maxLength = maxLength + + self.useCounts = useCounts + self.confId = confId + self.fromAtoms = fromAtoms + self.ignoreAtoms = ignoreAtoms + self.atomInvariants = atomInvariants + self._generate_fp_generator() + delattr(self, "_initializing") + + def _generate_fp_generator(self): + self._fpgen = GetAtomPairGenerator( + minDistance=int(self.minLength), + maxDistance=int(self.maxLength), + includeChirality=bool(self.includeChirality), + use2D=bool(self.use2D), + fpSize=int(self.fpSize), + ) + + def _transform_mol(self, mol) -> np.array: + if self.useCounts: + return self._fpgen.GetCountFingerprintAsNumPy( + mol, + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + customAtomInvariants=self.atomInvariants, + ) + else: + return self._fpgen.GetFingerprintAsNumPy( + mol, + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + customAtomInvariants=self.atomInvariants, + ) + + class AtomPairFPGeneratorTransformer(FpsGeneratorTransformer): _regenerate_on_properties = ( "fpSize", diff --git a/scikit_mol/fingerprints/rdkitfp.py b/scikit_mol/fingerprints/rdkitfp.py index ad87a26..13d9a27 100644 --- a/scikit_mol/fingerprints/rdkitfp.py +++ b/scikit_mol/fingerprints/rdkitfp.py @@ -11,7 +11,7 @@ from rdkit.Chem import rdFingerprintGenerator -class RDKitFingerprintTransformer(FpsTransformer): +class RDKitFingerprintTransformerClassic(FpsTransformer): def __init__( self, minPath: int = 1, @@ -88,6 +88,94 @@ def _mol2fp(self, mol): return generator.GetFingerprint(mol) +class RDKitFingerprintTransformer(FpsGeneratorTransformer): + _regenerate_on_properties = ( + "minPath", + "maxPath", + "useHs", + "branchedPaths", + "useBondOrder", + "countSimulation", + "fpSize", + "countBounds", + "numBitsPerFeature", + ) + + def __init__( + self, + minPath: int = 1, + maxPath: int = 7, + useHs: bool = True, + branchedPaths: bool = True, + useBondOrder: bool = True, + countSimulation: bool = False, + countBounds=None, + fpSize: int = 2048, + numBitsPerFeature: int = 2, + useCounts: bool = False, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + ): + """Calculates the RDKit fingerprints + + Parameters + ---------- + minPath : int, optional + the minimum path length (in bonds) to be included, by default 1 + maxPath : int, optional + the maximum path length (in bonds) to be included, by default 7 + useHs : bool, optional + toggles inclusion of Hs in paths (if the molecule has explicit Hs), by default True + branchedPaths : bool, optional + toggles generation of branched subgraphs, not just linear paths, by default True + useBondOrder : bool, optional + toggles inclusion of bond orders in the path hashes, by default True + countSimulation : bool, optional + if set, use count simulation while generating the fingerprint, by default False + countBounds : _type_, optional + boundaries for count simulation, corresponding bit will be set if the count is higher than the number provided for that spot, by default None + fpSize : int, optional + size of the generated fingerprint, does not affect the sparse versions, by default 2048 + numBitsPerFeature : int, optional + the number of bits set per path/subgraph found, by default 2 + """ + self._initializing = True + super().__init__(parallel=parallel, safe_inference_mode=safe_inference_mode) + self.minPath = minPath + self.maxPath = maxPath + self.useHs = useHs + self.branchedPaths = branchedPaths + self.useBondOrder = useBondOrder + self.countSimulation = countSimulation + self.fpSize = fpSize + self.numBitsPerFeature = numBitsPerFeature + self.countBounds = countBounds + + self.useCounts = useCounts + + self._generate_fp_generator() + delattr(self, "_initializing") + + def _transform_mol(self, mol) -> np.array: + if self.useCounts: + return self._fpgen.GetCountFingerprintAsNumPy(mol) + else: + return self._fpgen.GetFingerprintAsNumPy(mol) + + def _generate_fp_generator(self): + self._fpgen = GetRDKitFPGenerator( + minPath=int(self.minPath), + maxPath=int(self.maxPath), + useHs=bool(self.useHs), + branchedPaths=bool(self.branchedPaths), + useBondOrder=bool(self.useBondOrder), + countSimulation=bool(self.countSimulation), + fpSize=int(self.fpSize), + countBounds=bool(self.countBounds), + numBitsPerFeature=int(self.numBitsPerFeature), + ) + + class RDKitFPGeneratorTransformer(FpsGeneratorTransformer): _regenerate_on_properties = ( "minPath", diff --git a/scikit_mol/fingerprints/topologicaltorsion.py b/scikit_mol/fingerprints/topologicaltorsion.py index 63b68bf..f983bcc 100644 --- a/scikit_mol/fingerprints/topologicaltorsion.py +++ b/scikit_mol/fingerprints/topologicaltorsion.py @@ -10,7 +10,7 @@ from rdkit.Chem.rdFingerprintGenerator import GetTopologicalTorsionGenerator -class TopologicalTorsionFingerprintTransformer(FpsTransformer): +class TopologicalTorsionFingerprintTransformerClassic(FpsTransformer): def __init__( self, targetSize: int = 4, @@ -66,6 +66,61 @@ def _mol2fp(self, mol): ) +class TopologicalTorsionFingerprintTransformer(FpsGeneratorTransformer): + _regenerate_on_properties = ("fpSize", "includeChirality", "targetSize") + + def __init__( + self, + targetSize: int = 4, + fromAtoms=None, + ignoreAtoms=None, + atomInvariants=None, + confId=-1, + includeChirality: bool = False, + fpSize: int = 2048, + useCounts: bool = False, + parallel: Union[bool, int] = False, + safe_inference_mode: bool = False, + ): + self._initializing = True + super().__init__(parallel=parallel, safe_inference_mode=safe_inference_mode) + self.fpSize = fpSize + self.includeChirality = includeChirality + self.targetSize = targetSize + + self.fromAtoms = fromAtoms + self.ignoreAtoms = ignoreAtoms + self.atomInvariants = atomInvariants + self.confId = confId + self.useCounts = useCounts + + self._generate_fp_generator() + delattr(self, "_initializing") + + def _generate_fp_generator(self): + self._fpgen = GetTopologicalTorsionGenerator( + torsionAtomCount=int(self.targetSize), + includeChirality=bool(self.includeChirality), + fpSize=int(self.fpSize), + ) + + def _transform_mol(self, mol) -> np.array: + if self.useCounts: + return self._fpgen.GetCountFingerprintAsNumPy( + mol, + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + customAtomInvariants=self.atomInvariants, + ) + else: + return self._fpgen.GetFingerprintAsNumPy( + mol, + fromAtoms=self.fromAtoms, + ignoreAtoms=self.ignoreAtoms, + customAtomInvariants=self.atomInvariants, + ) + + class TopologicalTorsionFPGeneatorTransformer(FpsGeneratorTransformer): _regenerate_on_properties = ("fpSize", "includeChirality", "targetSize") diff --git a/tests/fixtures.py b/tests/fixtures.py index c3f392c..1434307 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -202,3 +202,18 @@ def combined_transformer(featurizer): @pytest.fixture def morgan_transformer(): return MorganFingerprintTransformer() + + +@pytest.fixture +def rdkit_transformer(): + return RDKitFingerprintTransformer() + + +@pytest.fixture +def atompair_transformer(): + return AtomPairFingerprintTransformer() + + +@pytest.fixture +def topologicaltorsion_transformer(): + return TopologicalTorsionFingerprintTransformer() diff --git a/tests/test_fptransformers.py b/tests/test_fptransformers.py index debf9ef..c977f7c 100644 --- a/tests/test_fptransformers.py +++ b/tests/test_fptransformers.py @@ -18,32 +18,17 @@ from sklearn import clone from scikit_mol.fingerprints import ( - MorganFingerprintTransformer, + # MorganFingerprintTransformer, MACCSKeysFingerprintTransformer, - RDKitFingerprintTransformer, - AtomPairFingerprintTransformer, - TopologicalTorsionFingerprintTransformer, + # RDKitFingerprintTransformer, + # AtomPairFingerprintTransformer, + # TopologicalTorsionFingerprintTransformer, SECFingerprintTransformer, MHFingerprintTransformer, AvalonFingerprintTransformer, ) -@pytest.fixture -def rdkit_transformer(): - return RDKitFingerprintTransformer() - - -@pytest.fixture -def atompair_transformer(): - return AtomPairFingerprintTransformer() - - -@pytest.fixture -def topologicaltorsion_transformer(): - return TopologicalTorsionFingerprintTransformer() - - @pytest.fixture def maccs_transformer(): return MACCSKeysFingerprintTransformer() @@ -84,9 +69,9 @@ def avalon_transformer(): def test_clonability( maccs_transformer, # morgan_transformer, - rdkit_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # rdkit_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, secfp_transformer, mhfp_transformer, avalon_transformer, @@ -94,9 +79,9 @@ def test_clonability( for t in [ maccs_transformer, # morgan_transformer, - rdkit_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # rdkit_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, secfp_transformer, mhfp_transformer, avalon_transformer, @@ -112,17 +97,17 @@ def test_clonability( def test_set_params( # morgan_transformer, - rdkit_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # rdkit_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, secfp_transformer, mhfp_transformer, avalon_transformer, ): for t in [ # morgan_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, avalon_transformer, ]: params = t.get_params() @@ -134,7 +119,7 @@ def test_set_params( params_2 = t.get_params() assert all([params[key] == params_2[key] for key in params.keys()]) - for t in [rdkit_transformer, secfp_transformer, mhfp_transformer]: + for t in [secfp_transformer, mhfp_transformer]: params = t.get_params() params["fpSize"] = 4242 t.set_params(fpSize=4242) @@ -145,9 +130,9 @@ def test_set_params( def test_transform( mols_container, # morgan_transformer, - rdkit_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # rdkit_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, maccs_transformer, secfp_transformer, mhfp_transformer, @@ -156,10 +141,10 @@ def test_transform( # Test the different transformers for t in [ # morgan_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, maccs_transformer, - rdkit_transformer, + # rdkit_transformer, secfp_transformer, mhfp_transformer, avalon_transformer, @@ -179,9 +164,9 @@ def test_transform( def test_transform_parallel( mols_container, # morgan_transformer, - rdkit_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # rdkit_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, maccs_transformer, secfp_transformer, mhfp_transformer, @@ -190,10 +175,10 @@ def test_transform_parallel( # Test the different transformers for t in [ # morgan_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, maccs_transformer, - rdkit_transformer, + # rdkit_transformer, secfp_transformer, mhfp_transformer, avalon_transformer, @@ -212,9 +197,9 @@ def test_transform_parallel( def test_picklable( # morgan_transformer, - rdkit_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # rdkit_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, maccs_transformer, secfp_transformer, avalon_transformer, @@ -222,10 +207,10 @@ def test_picklable( # Test the different transformers for t in [ # morgan_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, maccs_transformer, - rdkit_transformer, + # rdkit_transformer, secfp_transformer, avalon_transformer, ]: @@ -269,74 +254,6 @@ def assert_transformer_set_params(tr_class, new_params, mols_list): ), f"Assertation error, FP appears to be different, although the {key} should be changed back as well as initialized to {params[key]}" -def test_morgan_set_params(chiral_mols_list): - new_params = { - "fpSize": 1024, - "radius": 1, - "useBondTypes": False, # TODO, why doesn't this change the FP? - "useChirality": True, - "useCounts": True, - "useFeatures": True, - } - - assert_transformer_set_params( - MorganFingerprintTransformer, new_params, chiral_mols_list - ) - - -def test_atompairs_set_params(chiral_mols_list): - new_params = { - #'atomInvariants': 1, - #'confId': -1, - #'fromAtoms': 1, - #'ignoreAtoms': 0, - "includeChirality": True, - "maxLength": 3, - "minLength": 3, - "fpSize": 1024, - "nBitsPerEntry": 3, - #'use2D': True, #TODO, understand why this can't be set different - "useCounts": True, - } - - assert_transformer_set_params( - AtomPairFingerprintTransformer, new_params, chiral_mols_list - ) - - -def test_topologicaltorsion_set_params(chiral_mols_list): - new_params = { #'atomInvariants': 0, - #'fromAtoms': 0, - #'ignoreAtoms': 0, - #'includeChirality': True, #TODO, figure out why this setting seems to give same FP wheter toggled or not - "fpSize": 1024, - "nBitsPerEntry": 3, - "targetSize": 5, - "useCounts": True, - } - - assert_transformer_set_params( - TopologicalTorsionFingerprintTransformer, new_params, chiral_mols_list - ) - - -def test_RDKitFPTransformer(chiral_mols_list): - new_params = { #'atomInvariantsGenerator': None, - #'branchedPaths': False, - #'countBounds': 0, #TODO: What does this do? - "countSimulation": True, - "fpSize": 1024, - "maxPath": 3, - "minPath": 2, - "numBitsPerFeature": 3, - "useBondOrder": False, # TODO, why doesn't this change the FP? - #'useHs': False, #TODO, why doesn't this change the FP? - } - assert_transformer_set_params( - RDKitFingerprintTransformer, new_params, chiral_mols_list - ) - - def test_SECFingerprintTransformer(chiral_mols_list): new_params = { "isomeric": True, @@ -383,19 +300,19 @@ def test_AvalonFingerprintTransformer(chiral_mols_list): def test_transform_with_safe_inference_mode( mols_with_invalid_container, # morgan_transformer, - rdkit_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # rdkit_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, maccs_transformer, secfp_transformer, avalon_transformer, ): for t in [ # morgan_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, maccs_transformer, - rdkit_transformer, + # rdkit_transformer, secfp_transformer, avalon_transformer, ]: @@ -415,9 +332,9 @@ def test_transform_with_safe_inference_mode( def test_transform_without_safe_inference_mode( mols_with_invalid_container, # morgan_transformer, - rdkit_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # rdkit_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, maccs_transformer, secfp_transformer, avalon_transformer, @@ -425,10 +342,10 @@ def test_transform_without_safe_inference_mode( ): for t in [ # morgan_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, maccs_transformer, - rdkit_transformer, + # rdkit_transformer, secfp_transformer, avalon_transformer, ]: @@ -444,19 +361,19 @@ def test_transform_without_safe_inference_mode( def test_transform_parallel_with_safe_inference_mode( mols_with_invalid_container, # morgan_transformer, - rdkit_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # rdkit_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, maccs_transformer, secfp_transformer, avalon_transformer, ): for t in [ - # morgan_transformer, - atompair_transformer, - topologicaltorsion_transformer, + # # morgan_transformer, + # atompair_transformer, + # topologicaltorsion_transformer, maccs_transformer, - rdkit_transformer, + # rdkit_transformer, secfp_transformer, avalon_transformer, ]: diff --git a/tests/test_fptransformersgenerator.py b/tests/test_fptransformersgenerator.py index f35771b..aa7a426 100644 --- a/tests/test_fptransformersgenerator.py +++ b/tests/test_fptransformersgenerator.py @@ -14,30 +14,20 @@ from sklearn import clone from scikit_mol.fingerprints import ( - MorganFPGeneratorTransformer, + AtomPairFingerprintTransformer, MorganFingerprintTransformer, - RDKitFPGeneratorTransformer, - AtomPairFPGeneratorTransformer, - TopologicalTorsionFPGeneatorTransformer, + RDKitFingerprintTransformer, + TopologicalTorsionFingerprintTransformer, ) test_transformers = [ + AtomPairFingerprintTransformer, MorganFingerprintTransformer, - MorganFPGeneratorTransformer, - RDKitFPGeneratorTransformer, - AtomPairFPGeneratorTransformer, - TopologicalTorsionFPGeneatorTransformer, + RDKitFingerprintTransformer, + TopologicalTorsionFingerprintTransformer, ] -# @pytest.mark.parametrize("transformer_class", test_transformers) -# def test_fpstransformer_fp2array(transformer_class, fingerprint): -# transformer = transformer_class() - -# with pytest.raises(DeprecationWarning, match='Generators can directly return fingerprints'): -# fp = transformer._fp2array(fingerprint) - - @pytest.mark.parametrize("transformer_class", test_transformers) def test_fpstransformer_transform_mol(transformer_class, mols_list): transformer = transformer_class() @@ -47,14 +37,12 @@ def test_fpstransformer_transform_mol(transformer_class, mols_list): assert type(fp) == type(np.array([0])) assert fp.shape == (2048,) - if isinstance(transformer, RDKitFPGeneratorTransformer): + if isinstance(transformer, RDKitFingerprintTransformer): assert fp.sum() == 104 - elif isinstance(transformer, AtomPairFPGeneratorTransformer): + elif isinstance(transformer, AtomPairFingerprintTransformer): assert fp.sum() == 32 - elif isinstance(transformer, TopologicalTorsionFPGeneatorTransformer): + elif isinstance(transformer, TopologicalTorsionFingerprintTransformer): assert fp.sum() == 12 - elif isinstance(transformer, MorganFPGeneratorTransformer): - assert fp.sum() == 14 elif isinstance(transformer, MorganFingerprintTransformer): assert fp.sum() == 14 else: @@ -177,7 +165,7 @@ def test_morgan_set_params(chiral_mols_list): } assert_transformer_set_params( - MorganFPGeneratorTransformer, new_params, chiral_mols_list + MorganFingerprintTransformer, new_params, chiral_mols_list ) @@ -191,13 +179,13 @@ def test_atompairs_set_params(chiral_mols_list): "maxLength": 3, "minLength": 3, "fpSize": 1024, - #'nBitsPerEntry': 3, #Todo: not setable with the generators? + #'nBitsPerEntry': 3, #TODO: seem deprecated with the generators? #'use2D': True, #TODO, understand why this can't be set different "useCounts": True, } assert_transformer_set_params( - AtomPairFPGeneratorTransformer, new_params, chiral_mols_list + AtomPairFingerprintTransformer, new_params, chiral_mols_list ) @@ -213,7 +201,7 @@ def test_topologicaltorsion_set_params(chiral_mols_list): } assert_transformer_set_params( - TopologicalTorsionFPGeneatorTransformer, new_params, chiral_mols_list + TopologicalTorsionFingerprintTransformer, new_params, chiral_mols_list ) @@ -230,5 +218,5 @@ def test_RDKitFPTransformer(chiral_mols_list): #'useHs': False, #TODO, why doesn't this change the FP? } assert_transformer_set_params( - RDKitFPGeneratorTransformer, new_params, chiral_mols_list + RDKitFingerprintTransformer, new_params, chiral_mols_list ) diff --git a/tests/test_parameter_types.py b/tests/test_parameter_types.py index ceea00c..4b73959 100644 --- a/tests/test_parameter_types.py +++ b/tests/test_parameter_types.py @@ -1,11 +1,15 @@ import pytest import numpy as np from rdkit import Chem -from fixtures import mols_list, smiles_list, morgan_transformer -from test_fptransformers import ( +from fixtures import ( + mols_list, + smiles_list, + morgan_transformer, atompair_transformer, topologicaltorsion_transformer, rdkit_transformer, +) +from test_fptransformers import ( avalon_transformer, ) From 0b0f0fc0422c93e7a6c5ad572cf423d9aa783a36 Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Sun, 24 Nov 2024 09:41:34 +0100 Subject: [PATCH 21/24] updated gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 6996e69..5d2fa4a 100644 --- a/.gitignore +++ b/.gitignore @@ -138,6 +138,6 @@ tests/data/ # setuptools_scm version scikit_mol/_version.py -notebooks/sandbox.py .vscode notebooks/SLC6A4_active_excape_export.csv +sandbox/ From 570119271ae04d2eec1a987b9262c57a974ef726 Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Sun, 24 Nov 2024 10:02:02 +0100 Subject: [PATCH 22/24] Fixed some issues that created deprecation warnings. All tests passes, and only warnings from dependencies --- scikit_mol/fingerprints/baseclasses.py | 23 +++---- scikit_mol/fingerprints/minhash.py | 16 +++-- tests/test_fptransformers.py | 88 +------------------------- 3 files changed, 22 insertions(+), 105 deletions(-) diff --git a/scikit_mol/fingerprints/baseclasses.py b/scikit_mol/fingerprints/baseclasses.py index 30bfc5c..e28fa07 100644 --- a/scikit_mol/fingerprints/baseclasses.py +++ b/scikit_mol/fingerprints/baseclasses.py @@ -132,19 +132,9 @@ def fit(self, X, y=None): @check_transform_input def _transform(self, X): if self.safe_inference_mode: - # Use the new method with masked arrays if we're in safe inference mode arrays = [self._safe_transform_mol(mol) for mol in X] return np.ma.stack(arrays) - elif hasattr( - self, "dtype" - ): # TODO, it seems a bit of a code smell that we have to preemptively test a property from the baseclass? - # Use the original, faster method if we're not in safe inference mode - # This also triggers a deprecation warning! - arr = np.zeros((len(X), self.fpSize), dtype=self.dtype) - for i, mol in enumerate(X): - arr[i, :] = self._transform_mol(mol) - return arr - else: # We are unsure on the dtype, so we don't use a preassigned array #TODO test time differnece to previous + else: arrays = [self._transform_mol(mol) for mol in X] return np.stack(arrays) @@ -237,6 +227,17 @@ def _fp2array(self, fp): else: return np.ma.masked_all((self.fpSize,), dtype=self.dtype) + @check_transform_input + def _transform(self, X): + if self.safe_inference_mode: + arrays = [self._safe_transform_mol(mol) for mol in X] + return np.ma.stack(arrays) + else: + arr = np.zeros((len(X), self.fpSize), dtype=self.dtype) + for i, mol in enumerate(X): + arr[i, :] = self._transform_mol(mol) + return arr + # TODO, remove when finally deprecating nBits def _get_param_names(self): """Get parameter names excluding deprecated parameters""" diff --git a/scikit_mol/fingerprints/minhash.py b/scikit_mol/fingerprints/minhash.py index 9d0ec31..e487739 100644 --- a/scikit_mol/fingerprints/minhash.py +++ b/scikit_mol/fingerprints/minhash.py @@ -75,7 +75,9 @@ def _fp2array(self, fp): return np.array(fp) def _recreate_encoder(self): - self.mhfp_encoder = rdMHFPFingerprint.MHFPEncoder(self.fpSize, self._seed) + self.mhfp_encoder = rdMHFPFingerprint.MHFPEncoder( + int(self.fpSize), int(self._seed) + ) @property def seed(self): @@ -166,12 +168,12 @@ def __setstate__(self, state): def _mol2fp(self, mol): return self.mhfp_encoder.EncodeSECFPMol( mol, - self.radius, - self.rings, - self.isomeric, - self.kekulize, - self.min_radius, - self.length, + int(self.radius), + bool(self.rings), + bool(self.isomeric), + bool(self.kekulize), + int(self.min_radius), + int(self.fpSize), ) def _recreate_encoder(self): diff --git a/tests/test_fptransformers.py b/tests/test_fptransformers.py index c977f7c..aa3ae3d 100644 --- a/tests/test_fptransformers.py +++ b/tests/test_fptransformers.py @@ -18,11 +18,7 @@ from sklearn import clone from scikit_mol.fingerprints import ( - # MorganFingerprintTransformer, MACCSKeysFingerprintTransformer, - # RDKitFingerprintTransformer, - # AtomPairFingerprintTransformer, - # TopologicalTorsionFingerprintTransformer, SECFingerprintTransformer, MHFingerprintTransformer, AvalonFingerprintTransformer, @@ -49,39 +45,14 @@ def avalon_transformer(): return AvalonFingerprintTransformer() -# morgan is no longer a fptransformer but a generator transformer, but why was this the only one to be tested here? -# def test_fpstransformer_fp2array(morgan_transformer, fingerprint): -# fp = morgan_transformer._fp2array(fingerprint) -# # See that fp is the correct type, shape and bit count -# assert type(fp) == type(np.array([0])) -# assert fp.shape == (1000,) -# assert fp.sum() == 25 - - -# def test_fpstransformer_transform_mol(morgan_transformer, mols_list): -# fp = morgan_transformer._transform_mol(mols_list[0]) -# # See that fp is the correct type, shape and bit count -# assert type(fp) == type(np.array([0])) -# assert fp.shape == (2048,) -# assert fp.sum() == 14 - - def test_clonability( maccs_transformer, - # morgan_transformer, - # rdkit_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, secfp_transformer, mhfp_transformer, avalon_transformer, ): for t in [ maccs_transformer, - # morgan_transformer, - # rdkit_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, secfp_transformer, mhfp_transformer, avalon_transformer, @@ -96,20 +67,11 @@ def test_clonability( def test_set_params( - # morgan_transformer, - # rdkit_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, secfp_transformer, mhfp_transformer, avalon_transformer, ): - for t in [ - # morgan_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, - avalon_transformer, - ]: + for t in [avalon_transformer]: params = t.get_params() # change extracted dictionary params["fpSize"] = 4242 @@ -129,10 +91,6 @@ def test_set_params( def test_transform( mols_container, - # morgan_transformer, - # rdkit_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, maccs_transformer, secfp_transformer, mhfp_transformer, @@ -140,11 +98,7 @@ def test_transform( ): # Test the different transformers for t in [ - # morgan_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, maccs_transformer, - # rdkit_transformer, secfp_transformer, mhfp_transformer, avalon_transformer, @@ -163,10 +117,6 @@ def test_transform( def test_transform_parallel( mols_container, - # morgan_transformer, - # rdkit_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, maccs_transformer, secfp_transformer, mhfp_transformer, @@ -174,11 +124,7 @@ def test_transform_parallel( ): # Test the different transformers for t in [ - # morgan_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, maccs_transformer, - # rdkit_transformer, secfp_transformer, mhfp_transformer, avalon_transformer, @@ -196,21 +142,13 @@ def test_transform_parallel( def test_picklable( - # morgan_transformer, - # rdkit_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, maccs_transformer, secfp_transformer, avalon_transformer, ): # Test the different transformers for t in [ - # morgan_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, maccs_transformer, - # rdkit_transformer, secfp_transformer, avalon_transformer, ]: @@ -299,20 +237,12 @@ def test_AvalonFingerprintTransformer(chiral_mols_list): def test_transform_with_safe_inference_mode( mols_with_invalid_container, - # morgan_transformer, - # rdkit_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, maccs_transformer, secfp_transformer, avalon_transformer, ): for t in [ - # morgan_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, maccs_transformer, - # rdkit_transformer, secfp_transformer, avalon_transformer, ]: @@ -331,21 +261,13 @@ def test_transform_with_safe_inference_mode( def test_transform_without_safe_inference_mode( mols_with_invalid_container, - # morgan_transformer, - # rdkit_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, maccs_transformer, secfp_transformer, avalon_transformer, # MHFP seem to accept invalid mols and return 0,0,0,0's ): for t in [ - # morgan_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, maccs_transformer, - # rdkit_transformer, secfp_transformer, avalon_transformer, ]: @@ -360,20 +282,12 @@ def test_transform_without_safe_inference_mode( # Add this test to check parallel processing with error handling def test_transform_parallel_with_safe_inference_mode( mols_with_invalid_container, - # morgan_transformer, - # rdkit_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, maccs_transformer, secfp_transformer, avalon_transformer, ): for t in [ - # # morgan_transformer, - # atompair_transformer, - # topologicaltorsion_transformer, maccs_transformer, - # rdkit_transformer, secfp_transformer, avalon_transformer, ]: From 35cfdc14fe5efd85a7fbf94941884d04685b4b0b Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Sun, 24 Nov 2024 10:06:41 +0100 Subject: [PATCH 23/24] Deleted the generator and classic temporary classes and updated tests --- scikit_mol/fingerprints/__init__.py | 7 +- scikit_mol/fingerprints/atompair.py | 134 -------------- scikit_mol/fingerprints/morgan.py | 145 --------------- scikit_mol/fingerprints/rdkitfp.py | 165 ------------------ scikit_mol/fingerprints/topologicaltorsion.py | 111 ------------ tests/test_transformers.py | 37 ---- 6 files changed, 3 insertions(+), 596 deletions(-) diff --git a/scikit_mol/fingerprints/__init__.py b/scikit_mol/fingerprints/__init__.py index 5ed655d..c0b4cb7 100644 --- a/scikit_mol/fingerprints/__init__.py +++ b/scikit_mol/fingerprints/__init__.py @@ -3,13 +3,12 @@ FpsGeneratorTransformer, ) # TODO, for backwards compatibility with tests, needs to be removed -from .atompair import AtomPairFingerprintTransformer, AtomPairFPGeneratorTransformer +from .atompair import AtomPairFingerprintTransformer from .avalon import AvalonFingerprintTransformer from .maccs import MACCSKeysFingerprintTransformer from .minhash import MHFingerprintTransformer, SECFingerprintTransformer -from .morgan import MorganFingerprintTransformer, MorganFPGeneratorTransformer -from .rdkitfp import RDKitFingerprintTransformer, RDKitFPGeneratorTransformer +from .morgan import MorganFingerprintTransformer +from .rdkitfp import RDKitFingerprintTransformer from .topologicaltorsion import ( TopologicalTorsionFingerprintTransformer, - TopologicalTorsionFPGeneatorTransformer, ) diff --git a/scikit_mol/fingerprints/atompair.py b/scikit_mol/fingerprints/atompair.py index f468575..ded1f18 100644 --- a/scikit_mol/fingerprints/atompair.py +++ b/scikit_mol/fingerprints/atompair.py @@ -10,74 +10,6 @@ from rdkit.Chem import rdMolDescriptors -class AtomPairFingerprintTransformerClassic(FpsTransformer): - def __init__( - self, - minLength: int = 1, - maxLength: int = 30, - fromAtoms=0, - ignoreAtoms=0, - atomInvariants=0, - nBitsPerEntry: int = 4, - includeChirality: bool = False, - use2D: bool = True, - confId: int = -1, - fpSize=2048, - useCounts: bool = False, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = np.int8, - ): - super().__init__( - parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype - ) - self.minLength = minLength - self.maxLength = maxLength - self.fromAtoms = fromAtoms - self.ignoreAtoms = ignoreAtoms - self.atomInvariants = atomInvariants - self.includeChirality = includeChirality - self.use2D = use2D - self.confId = confId - self.fpSize = fpSize - self.nBitsPerEntry = nBitsPerEntry - self.useCounts = useCounts - - warn( - "AtomPairFingerprintTransformer will be replace by AtomPairFPGeneratorTransformer, due to changes in RDKit!", - DeprecationWarning, - ) - - def _mol2fp(self, mol): - if self.useCounts: - return rdMolDescriptors.GetHashedAtomPairFingerprint( - mol, - nBits=int(self.fpSize), - minLength=int(self.minLength), - maxLength=int(self.maxLength), - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - atomInvariants=self.atomInvariants, - includeChirality=bool(self.includeChirality), - use2D=bool(self.use2D), - confId=int(self.confId), - ) - else: - return rdMolDescriptors.GetHashedAtomPairFingerprintAsBitVect( - mol, - nBits=int(self.fpSize), - minLength=int(self.minLength), - maxLength=int(self.maxLength), - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - atomInvariants=self.atomInvariants, - nBitsPerEntry=int(self.nBitsPerEntry), - includeChirality=bool(self.includeChirality), - use2D=bool(self.use2D), - confId=int(self.confId), - ) - - class AtomPairFingerprintTransformer(FpsGeneratorTransformer): _regenerate_on_properties = ( "fpSize", @@ -142,69 +74,3 @@ def _transform_mol(self, mol) -> np.array: ignoreAtoms=self.ignoreAtoms, customAtomInvariants=self.atomInvariants, ) - - -class AtomPairFPGeneratorTransformer(FpsGeneratorTransformer): - _regenerate_on_properties = ( - "fpSize", - "includeChirality", - "use2D", - "minLength", - "maxLength", - ) - - def __init__( - self, - minLength: int = 1, - maxLength: int = 30, - fromAtoms=None, - ignoreAtoms=None, - atomInvariants=None, - includeChirality: bool = False, - use2D: bool = True, - confId: int = -1, - fpSize: int = 2048, - useCounts: bool = False, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - ): - self._initializing = True - super().__init__(parallel=parallel, safe_inference_mode=safe_inference_mode) - self.fpSize = fpSize - self.use2D = use2D - self.includeChirality = includeChirality - self.minLength = minLength - self.maxLength = maxLength - - self.useCounts = useCounts - self.confId = confId - self.fromAtoms = fromAtoms - self.ignoreAtoms = ignoreAtoms - self.atomInvariants = atomInvariants - self._generate_fp_generator() - delattr(self, "_initializing") - - def _generate_fp_generator(self): - self._fpgen = GetAtomPairGenerator( - minDistance=self.minLength, - maxDistance=self.maxLength, - includeChirality=self.includeChirality, - use2D=self.use2D, - fpSize=self.fpSize, - ) - - def _transform_mol(self, mol) -> np.array: - if self.useCounts: - return self._fpgen.GetCountFingerprintAsNumPy( - mol, - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - customAtomInvariants=self.atomInvariants, - ) - else: - return self._fpgen.GetFingerprintAsNumPy( - mol, - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - customAtomInvariants=self.atomInvariants, - ) diff --git a/scikit_mol/fingerprints/morgan.py b/scikit_mol/fingerprints/morgan.py index 6ddb6b9..f2b1edf 100644 --- a/scikit_mol/fingerprints/morgan.py +++ b/scikit_mol/fingerprints/morgan.py @@ -14,72 +14,6 @@ from .baseclasses import FpsTransformer, FpsGeneratorTransformer -class MorganFingerprintTransformerClassic(FpsTransformer): - def __init__( - self, - fpSize=2048, - radius=2, - useChirality=False, - useBondTypes=True, - useFeatures=False, - useCounts=False, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = np.int8, - ): - """Transform RDKit mols into Count or bit-based hashed MorganFingerprints - - Parameters - ---------- - fpSize : int, optional - Size of the hashed fingerprint, by default 2048 - radius : int, optional - Radius of the fingerprint, by default 2 - useChirality : bool, optional - Include chirality in calculation of the fingerprint keys, by default False - useBondTypes : bool, optional - Include bondtypes in calculation of the fingerprint keys, by default True - useFeatures : bool, optional - use chemical features, rather than atom-type in calculation of the fingerprint keys, by default False - useCounts : bool, optional - If toggled will create the count and not bit-based fingerprint, by default False - """ - super().__init__( - parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype - ) - self.fpSize = fpSize - self.radius = radius - self.useChirality = useChirality - self.useBondTypes = useBondTypes - self.useFeatures = useFeatures - self.useCounts = useCounts - - warn( - "MorganFingerprintTransformer will be replace by MorganGeneratorTransformer, due to changes in RDKit!", - DeprecationWarning, - ) - - def _mol2fp(self, mol): - if self.useCounts: - return rdMolDescriptors.GetHashedMorganFingerprint( - mol, - int(self.radius), - nBits=int(self.fpSize), - useFeatures=bool(self.useFeatures), - useChirality=bool(self.useChirality), - useBondTypes=bool(self.useBondTypes), - ) - else: - return rdMolDescriptors.GetMorganFingerprintAsBitVect( - mol, - int(self.radius), - nBits=int(self.fpSize), - useFeatures=bool(self.useFeatures), - useChirality=bool(self.useChirality), - useBondTypes=bool(self.useBondTypes), - ) - - class MorganFingerprintTransformer(FpsGeneratorTransformer): _regenerate_on_properties = ( "radius", @@ -157,82 +91,3 @@ def _transform_mol(self, mol) -> np.array: return self._fpgen.GetCountFingerprintAsNumPy(mol) else: return self._fpgen.GetFingerprintAsNumPy(mol) - - -class MorganFPGeneratorTransformer(FpsGeneratorTransformer): - _regenerate_on_properties = ( - "radius", - "fpSize", - "useChirality", - "useFeatures", - "useBondTypes", - ) - - def __init__( - self, - fpSize=2048, - radius=2, - useChirality=False, - useBondTypes=True, - useFeatures=False, - useCounts=False, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = None, - nBits: int = None, - ): - """Transform RDKit mols into Count or bit-based hashed MorganFingerprints - - Parameters - ---------- - fpsize : int, optional - Size of the hashed fingerprint, by default 2048 - radius : int, optional - Radius of the fingerprint, by default 2 - useChirality : bool, optional - Include chirality in calculation of the fingerprint keys, by default False - useBondTypes : bool, optional - Include bondtypes in calculation of the fingerprint keys, by default True - useFeatures : bool, optional - use chemical features, rather than atom-type in calculation of the fingerprint keys, by default False - useCounts : bool, optional - If toggled will create the count and not bit-based fingerprint, by default False - parallel : bool or int, optional - If True, will use all available cores, if int will use that many cores, by default False - safe_inference_mode : bool, optional - If True, will return masked arrays for invalid mols, by default False - """ - - self._initializing = True - super().__init__(parallel=parallel, safe_inference_mode=safe_inference_mode) - self.fpSize = fpSize - self.radius = radius - self.useChirality = useChirality - self.useFeatures = useFeatures - self.useCounts = useCounts - self.useBondTypes = useBondTypes - self.dtype = dtype - self.nBits = nBits - - self._generate_fp_generator() - delattr(self, "_initializing") - - def _generate_fp_generator(self): - if self.useFeatures: - atomInvariantsGenerator = GetMorganFeatureAtomInvGen() - else: - atomInvariantsGenerator = None - - self._fpgen = GetMorganGenerator( - radius=int(self.radius), - fpSize=int(self.fpSize), - includeChirality=bool(self.useChirality), - useBondTypes=bool(self.useBondTypes), - atomInvariantsGenerator=atomInvariantsGenerator, - ) - - def _transform_mol(self, mol) -> np.array: - if self.useCounts: - return self._fpgen.GetCountFingerprintAsNumPy(mol) - else: - return self._fpgen.GetFingerprintAsNumPy(mol) diff --git a/scikit_mol/fingerprints/rdkitfp.py b/scikit_mol/fingerprints/rdkitfp.py index 13d9a27..19a8d2e 100644 --- a/scikit_mol/fingerprints/rdkitfp.py +++ b/scikit_mol/fingerprints/rdkitfp.py @@ -11,83 +11,6 @@ from rdkit.Chem import rdFingerprintGenerator -class RDKitFingerprintTransformerClassic(FpsTransformer): - def __init__( - self, - minPath: int = 1, - maxPath: int = 7, - useHs: bool = True, - branchedPaths: bool = True, - useBondOrder: bool = True, - countSimulation: bool = False, - countBounds=None, - fpSize: int = 2048, - numBitsPerFeature: int = 2, - atomInvariantsGenerator=None, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = np.int8, - ): - """Calculates the RDKit fingerprints - - Parameters - ---------- - minPath : int, optional - the minimum path length (in bonds) to be included, by default 1 - maxPath : int, optional - the maximum path length (in bonds) to be included, by default 7 - useHs : bool, optional - toggles inclusion of Hs in paths (if the molecule has explicit Hs), by default True - branchedPaths : bool, optional - toggles generation of branched subgraphs, not just linear paths, by default True - useBondOrder : bool, optional - toggles inclusion of bond orders in the path hashes, by default True - countSimulation : bool, optional - if set, use count simulation while generating the fingerprint, by default False - countBounds : _type_, optional - boundaries for count simulation, corresponding bit will be set if the count is higher than the number provided for that spot, by default None - fpSize : int, optional - size of the generated fingerprint, does not affect the sparse versions, by default 2048 - numBitsPerFeature : int, optional - the number of bits set per path/subgraph found, by default 2 - atomInvariantsGenerator : _type_, optional - atom invariants to be used during fingerprint generation, by default None - """ - super().__init__( - parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype - ) - self.minPath = minPath - self.maxPath = maxPath - self.useHs = useHs - self.branchedPaths = branchedPaths - self.useBondOrder = useBondOrder - self.countSimulation = countSimulation - self.countBounds = countBounds - self.fpSize = fpSize - self.numBitsPerFeature = numBitsPerFeature - self.atomInvariantsGenerator = atomInvariantsGenerator - - warn( - "RDKitFingerprintTransformer will be replace by RDKitFPGeneratorTransformer, due to changes in RDKit!", - DeprecationWarning, - ) - - def _mol2fp(self, mol): - generator = rdFingerprintGenerator.GetRDKitFPGenerator( - minPath=int(self.minPath), - maxPath=int(self.maxPath), - useHs=bool(self.useHs), - branchedPaths=bool(self.branchedPaths), - useBondOrder=bool(self.useBondOrder), - countSimulation=bool(self.countSimulation), - countBounds=bool(self.countBounds), - fpSize=int(self.fpSize), - numBitsPerFeature=int(self.numBitsPerFeature), - atomInvariantsGenerator=self.atomInvariantsGenerator, - ) - return generator.GetFingerprint(mol) - - class RDKitFingerprintTransformer(FpsGeneratorTransformer): _regenerate_on_properties = ( "minPath", @@ -174,91 +97,3 @@ def _generate_fp_generator(self): countBounds=bool(self.countBounds), numBitsPerFeature=int(self.numBitsPerFeature), ) - - -class RDKitFPGeneratorTransformer(FpsGeneratorTransformer): - _regenerate_on_properties = ( - "minPath", - "maxPath", - "useHs", - "branchedPaths", - "useBondOrder", - "countSimulation", - "fpSize", - "countBounds", - "numBitsPerFeature", - ) - - def __init__( - self, - minPath: int = 1, - maxPath: int = 7, - useHs: bool = True, - branchedPaths: bool = True, - useBondOrder: bool = True, - countSimulation: bool = False, - countBounds=None, - fpSize: int = 2048, - numBitsPerFeature: int = 2, - useCounts: bool = False, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - ): - """Calculates the RDKit fingerprints - - Parameters - ---------- - minPath : int, optional - the minimum path length (in bonds) to be included, by default 1 - maxPath : int, optional - the maximum path length (in bonds) to be included, by default 7 - useHs : bool, optional - toggles inclusion of Hs in paths (if the molecule has explicit Hs), by default True - branchedPaths : bool, optional - toggles generation of branched subgraphs, not just linear paths, by default True - useBondOrder : bool, optional - toggles inclusion of bond orders in the path hashes, by default True - countSimulation : bool, optional - if set, use count simulation while generating the fingerprint, by default False - countBounds : _type_, optional - boundaries for count simulation, corresponding bit will be set if the count is higher than the number provided for that spot, by default None - fpSize : int, optional - size of the generated fingerprint, does not affect the sparse versions, by default 2048 - numBitsPerFeature : int, optional - the number of bits set per path/subgraph found, by default 2 - """ - self._initializing = True - super().__init__(parallel=parallel, safe_inference_mode=safe_inference_mode) - self.minPath = minPath - self.maxPath = maxPath - self.useHs = useHs - self.branchedPaths = branchedPaths - self.useBondOrder = useBondOrder - self.countSimulation = countSimulation - self.fpSize = fpSize - self.numBitsPerFeature = numBitsPerFeature - self.countBounds = countBounds - - self.useCounts = useCounts - - self._generate_fp_generator() - delattr(self, "_initializing") - - def _transform_mol(self, mol) -> np.array: - if self.useCounts: - return self._fpgen.GetCountFingerprintAsNumPy(mol) - else: - return self._fpgen.GetFingerprintAsNumPy(mol) - - def _generate_fp_generator(self): - self._fpgen = GetRDKitFPGenerator( - minPath=self.minPath, - maxPath=self.maxPath, - useHs=self.useHs, - branchedPaths=self.branchedPaths, - useBondOrder=self.useBondOrder, - countSimulation=self.countSimulation, - fpSize=self.fpSize, - countBounds=self.countBounds, - numBitsPerFeature=self.numBitsPerFeature, - ) diff --git a/scikit_mol/fingerprints/topologicaltorsion.py b/scikit_mol/fingerprints/topologicaltorsion.py index f983bcc..0cd5d9e 100644 --- a/scikit_mol/fingerprints/topologicaltorsion.py +++ b/scikit_mol/fingerprints/topologicaltorsion.py @@ -10,62 +10,6 @@ from rdkit.Chem.rdFingerprintGenerator import GetTopologicalTorsionGenerator -class TopologicalTorsionFingerprintTransformerClassic(FpsTransformer): - def __init__( - self, - targetSize: int = 4, - fromAtoms=0, - ignoreAtoms=0, - atomInvariants=0, - includeChirality: bool = False, - nBitsPerEntry: int = 4, - fpSize=2048, - useCounts: bool = False, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - dtype: np.dtype = np.int8, - ): - super().__init__( - parallel=parallel, safe_inference_mode=safe_inference_mode, dtype=dtype - ) - self.targetSize = targetSize - self.fromAtoms = fromAtoms - self.ignoreAtoms = ignoreAtoms - self.atomInvariants = atomInvariants - self.includeChirality = includeChirality - self.nBitsPerEntry = nBitsPerEntry - self.fpSize = fpSize - self.useCounts = useCounts - - warn( - "TopologicalTorsionFingerprintTransformer will be replace by TopologicalTorsionFPGeneatorTransformer, due to changes in RDKit!", - DeprecationWarning, - ) - - def _mol2fp(self, mol): - if self.useCounts: - return rdMolDescriptors.GetHashedTopologicalTorsionFingerprint( - mol, - nBits=int(self.fpSize), - targetSize=int(self.targetSize), - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - atomInvariants=self.atomInvariants, - includeChirality=bool(self.includeChirality), - ) - else: - return rdMolDescriptors.GetHashedTopologicalTorsionFingerprintAsBitVect( - mol, - nBits=int(self.fpSize), - targetSize=int(self.targetSize), - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - atomInvariants=self.atomInvariants, - includeChirality=bool(self.includeChirality), - nBitsPerEntry=int(self.nBitsPerEntry), - ) - - class TopologicalTorsionFingerprintTransformer(FpsGeneratorTransformer): _regenerate_on_properties = ("fpSize", "includeChirality", "targetSize") @@ -119,58 +63,3 @@ def _transform_mol(self, mol) -> np.array: ignoreAtoms=self.ignoreAtoms, customAtomInvariants=self.atomInvariants, ) - - -class TopologicalTorsionFPGeneatorTransformer(FpsGeneratorTransformer): - _regenerate_on_properties = ("fpSize", "includeChirality", "targetSize") - - def __init__( - self, - targetSize: int = 4, - fromAtoms=None, - ignoreAtoms=None, - atomInvariants=None, - confId=-1, - includeChirality: bool = False, - fpSize: int = 2048, - useCounts: bool = False, - parallel: Union[bool, int] = False, - safe_inference_mode: bool = False, - ): - self._initializing = True - super().__init__(parallel=parallel, safe_inference_mode=safe_inference_mode) - self.fpSize = fpSize - self.includeChirality = includeChirality - self.targetSize = targetSize - - self.fromAtoms = fromAtoms - self.ignoreAtoms = ignoreAtoms - self.atomInvariants = atomInvariants - self.confId = confId - self.useCounts = useCounts - - self._generate_fp_generator() - delattr(self, "_initializing") - - def _generate_fp_generator(self): - self._fpgen = GetTopologicalTorsionGenerator( - torsionAtomCount=self.targetSize, - includeChirality=self.includeChirality, - fpSize=self.fpSize, - ) - - def _transform_mol(self, mol) -> np.array: - if self.useCounts: - return self._fpgen.GetCountFingerprintAsNumPy( - mol, - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - customAtomInvariants=self.atomInvariants, - ) - else: - return self._fpgen.GetFingerprintAsNumPy( - mol, - fromAtoms=self.fromAtoms, - ignoreAtoms=self.ignoreAtoms, - customAtomInvariants=self.atomInvariants, - ) diff --git a/tests/test_transformers.py b/tests/test_transformers.py index 9352441..a47b8bf 100644 --- a/tests/test_transformers.py +++ b/tests/test_transformers.py @@ -24,10 +24,6 @@ SECFingerprintTransformer, MHFingerprintTransformer, AvalonFingerprintTransformer, - MorganFPGeneratorTransformer, - RDKitFPGeneratorTransformer, - AtomPairFPGeneratorTransformer, - TopologicalTorsionFPGeneatorTransformer, ) from scikit_mol.fingerprints.baseclasses import BaseFpsTransformer @@ -50,13 +46,6 @@ def test_transformer(SLC6A4_subset): X_train, X_test = X_smiles[:128], X_smiles[128:] Y_train, Y_test = Y[:128], Y[128:] - (MorganFPGeneratorTransformer,) - ( - RDKitFPGeneratorTransformer, - AtomPairFPGeneratorTransformer, - TopologicalTorsionFPGeneatorTransformer, - ) - # run FP with default parameters except when useCounts can be given as an argument FP_dict = { "MACCSTransformer": [MACCSKeysFingerprintTransformer, None], @@ -79,19 +68,6 @@ def test_transformer(SLC6A4_subset): "SECFingerprintTransformer": [SECFingerprintTransformer, None], "MHFingerprintTransformer": [MHFingerprintTransformer, None], "AvalonFingerprintTransformer": [AvalonFingerprintTransformer, None], - "MorganFPGeneratorTransformer": [MorganFPGeneratorTransformer, True], - "MorganFPGeneratorTransformer": [MorganFPGeneratorTransformer, False], - "RDKitFPGeneratorTransformer": [RDKitFPGeneratorTransformer, None], - "AtomPairFPGeneratorTransformer": [AtomPairFPGeneratorTransformer, True], - "AtomPairFPGeneratorTransformer": [AtomPairFPGeneratorTransformer, False], - "TopologicalTorsionFPGeneatorTransformer": [ - TopologicalTorsionFPGeneatorTransformer, - True, - ], - "TopologicalTorsionFPGeneatorTransformer": [ - TopologicalTorsionFPGeneatorTransformer, - False, - ], } # fit on toy data and print train/test score if successful or collect the failed FP @@ -162,19 +138,6 @@ def test_transformer_pandas_output(SLC6A4_subset, pandas_output): "SECFingerprintTransformer": [SECFingerprintTransformer, None], "MHFingerprintTransformer": [MHFingerprintTransformer, None], "AvalonFingerprintTransformer": [AvalonFingerprintTransformer, None], - "MorganFPGeneratorTransformer": [MorganFPGeneratorTransformer, True], - "MorganFPGeneratorTransformer": [MorganFPGeneratorTransformer, False], - "RDKitFPGeneratorTransformer": [RDKitFPGeneratorTransformer, None], - "AtomPairFPGeneratorTransformer": [AtomPairFPGeneratorTransformer, True], - "AtomPairFPGeneratorTransformer": [AtomPairFPGeneratorTransformer, False], - "TopologicalTorsionFPGeneatorTransformer": [ - TopologicalTorsionFPGeneatorTransformer, - True, - ], - "TopologicalTorsionFPGeneatorTransformer": [ - TopologicalTorsionFPGeneatorTransformer, - False, - ], } # fit on toy data and check that the output is a pandas dataframe From f1bbc309e4804f0b8923f59aa9a684610b9a4f44 Mon Sep 17 00:00:00 2001 From: Esben Jannik Bjerrum Date: Sun, 24 Nov 2024 10:31:34 +0100 Subject: [PATCH 24/24] Updated and reran notebooks --- notebooks/01_basic_usage.ipynb | 564 +- notebooks/01_basic_usage.py | 19 +- notebooks/02_descriptor_transformer.ipynb | 78 +- notebooks/03_example_pipeline.ipynb | 130 +- notebooks/04_standardizer.ipynb | 112 +- notebooks/05_smiles_sanitaztion.ipynb | 76 +- notebooks/06_hyperparameter_tuning.ipynb | 257 +- notebooks/06_hyperparameter_tuning.py | 50 +- notebooks/07_parallel_transforms.ipynb | 4956 +++++++++++++++- notebooks/08_external_library_skopt.ipynb | 629 ++- notebooks/08_external_library_skopt.py | 36 +- ..._Usage_with_FingerPrint_Transformers.ipynb | 369 +- ...hod_Usage_with_FingerPrint_Transformers.py | 103 +- notebooks/10_pipeline_pandas_output.ipynb | 5024 +++++++++++++++-- notebooks/11_safe_inference.ipynb | 458 +- notebooks/11_safe_inference.py | 29 +- 16 files changed, 11552 insertions(+), 1338 deletions(-) diff --git a/notebooks/01_basic_usage.ipynb b/notebooks/01_basic_usage.ipynb index 4c62abe..e254859 100644 --- a/notebooks/01_basic_usage.ipynb +++ b/notebooks/01_basic_usage.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "8a3e313c", + "id": "aa079ac3", "metadata": {}, "source": [ "# Scikit-Mol\n", @@ -13,7 +13,7 @@ }, { "cell_type": "markdown", - "id": "7bcbed23", + "id": "76d24789", "metadata": {}, "source": [ "The transformer classes are easy to load, configure and use to process molecular information into vectorized formats using fingerprinters or collections of descriptors. For demonstration purposes, let's load a MorganTransformer, that can convert a list of RDKit molecular objects into a numpy array of morgan fingerprints. First create some molecules from SMILES strings." @@ -22,13 +22,13 @@ { "cell_type": "code", "execution_count": 1, - "id": "f8025236", + "id": "2c8cad03", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:09.802220Z", - "iopub.status.busy": "2024-04-12T12:10:09.802030Z", - "iopub.status.idle": "2024-04-12T12:10:09.808949Z", - "shell.execute_reply": "2024-04-12T12:10:09.808440Z" + "iopub.execute_input": "2024-11-24T09:27:16.292725Z", + "iopub.status.busy": "2024-11-24T09:27:16.292083Z", + "iopub.status.idle": "2024-11-24T09:27:16.306663Z", + "shell.execute_reply": "2024-11-24T09:27:16.304935Z" } }, "outputs": [], @@ -39,32 +39,34 @@ { "cell_type": "code", "execution_count": 2, - "id": "58a33f4d", + "id": "8d5b2333", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:09.811277Z", - "iopub.status.busy": "2024-04-12T12:10:09.811060Z", - "iopub.status.idle": "2024-04-12T12:10:09.936897Z", - "shell.execute_reply": "2024-04-12T12:10:09.936201Z" + "iopub.execute_input": "2024-11-24T09:27:16.313611Z", + "iopub.status.busy": "2024-11-24T09:27:16.313028Z", + "iopub.status.idle": "2024-11-24T09:27:16.510254Z", + "shell.execute_reply": "2024-11-24T09:27:16.509620Z" } }, "outputs": [], "source": [ "from rdkit import Chem\n", "\n", - "smiles_strings = [\"C12C([C@@H](OC(C=3C=CC(=CC3)F)C=4C=CC(=CC4)F)CC(N1CCCCCC5=CC=CC=C5)CC2)C(=O)OC\", \n", - "\"O(C1=NC=C2C(CN(CC2=C1)C)C3=CC=C(OC)C=C3)CCCN(CC)CC\",\n", - "\"O=S(=O)(N(CC=1C=CC2=CC=CC=C2C1)[C@@H]3CCNC3)C\",\n", - "\"C1(=C2C(CCCC2O)=NC=3C1=CC=CC3)NCC=4C=CC(=CC4)Cl\",\n", - "\"C1NC[C@@H](C1)[C@H](OC=2C=CC(=NC2C)OC)CC(C)C\",\n", - "\"FC(F)(F)C=1C(CN(C2CCNCC2)CC(CC)CC)=CC=CC1\"]\n", + "smiles_strings = [\n", + " \"C12C([C@@H](OC(C=3C=CC(=CC3)F)C=4C=CC(=CC4)F)CC(N1CCCCCC5=CC=CC=C5)CC2)C(=O)OC\",\n", + " \"O(C1=NC=C2C(CN(CC2=C1)C)C3=CC=C(OC)C=C3)CCCN(CC)CC\",\n", + " \"O=S(=O)(N(CC=1C=CC2=CC=CC=C2C1)[C@@H]3CCNC3)C\",\n", + " \"C1(=C2C(CCCC2O)=NC=3C1=CC=CC3)NCC=4C=CC(=CC4)Cl\",\n", + " \"C1NC[C@@H](C1)[C@H](OC=2C=CC(=NC2C)OC)CC(C)C\",\n", + " \"FC(F)(F)C=1C(CN(C2CCNCC2)CC(CC)CC)=CC=CC1\",\n", + "]\n", "\n", "mols = [Chem.MolFromSmiles(smiles) for smiles in smiles_strings]" ] }, { "cell_type": "markdown", - "id": "0228c878", + "id": "b9a588c7", "metadata": {}, "source": [ "Next we import the Morgan fingerprint transformer" @@ -73,13 +75,13 @@ { "cell_type": "code", "execution_count": 3, - "id": "cdb821a1", + "id": "0a625dda", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:09.939980Z", - "iopub.status.busy": "2024-04-12T12:10:09.939552Z", - "iopub.status.idle": "2024-04-12T12:10:10.505528Z", - "shell.execute_reply": "2024-04-12T12:10:10.504885Z" + "iopub.execute_input": "2024-11-24T09:27:16.513123Z", + "iopub.status.busy": "2024-11-24T09:27:16.512856Z", + "iopub.status.idle": "2024-11-24T09:27:17.089043Z", + "shell.execute_reply": "2024-11-24T09:27:17.088357Z" } }, "outputs": [ @@ -100,10 +102,10 @@ }, { "cell_type": "markdown", - "id": "e8ebae67", + "id": "355610d1", "metadata": {}, "source": [ - "It actually renders as a cute little interactive block in the Jupyter notebook and lists the options that are not the default values. If we print it, it also gives the information on the settings. \n", + "It actually renders as a cute little interactive block in the Jupyter notebook and lists the options that are not the default values. If we print it, it also gives the information on the settings.\n", "\n", "![An image of the interactive transformer widget](images/Transformer_Widget.jpg \"Transformer object rendering in Jupyter\")\n", "\n", @@ -113,20 +115,424 @@ { "cell_type": "code", "execution_count": 4, - "id": "c3a24f4e", + "id": "9a801d0f", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:10.508400Z", - "iopub.status.busy": "2024-04-12T12:10:10.508055Z", - "iopub.status.idle": "2024-04-12T12:10:10.514636Z", - "shell.execute_reply": "2024-04-12T12:10:10.514117Z" + "iopub.execute_input": "2024-11-24T09:27:17.091942Z", + "iopub.status.busy": "2024-11-24T09:27:17.091571Z", + "iopub.status.idle": "2024-11-24T09:27:17.098501Z", + "shell.execute_reply": "2024-11-24T09:27:17.097922Z" } }, "outputs": [ { "data": { "text/html": [ - "
MorganFingerprintTransformer(radius=3)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + "
MorganFingerprintTransformer(radius=3)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "MorganFingerprintTransformer(radius=3)" @@ -143,7 +549,7 @@ }, { "cell_type": "markdown", - "id": "c6e5de37", + "id": "556858b4", "metadata": {}, "source": [ "If we want to get all the settings explicitly, we can use the .get_params() method." @@ -152,22 +558,23 @@ { "cell_type": "code", "execution_count": 5, - "id": "112afff2", + "id": "500dc6f7", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:10.517110Z", - "iopub.status.busy": "2024-04-12T12:10:10.516867Z", - "iopub.status.idle": "2024-04-12T12:10:10.521207Z", - "shell.execute_reply": "2024-04-12T12:10:10.520689Z" + "iopub.execute_input": "2024-11-24T09:27:17.101153Z", + "iopub.status.busy": "2024-11-24T09:27:17.100929Z", + "iopub.status.idle": "2024-11-24T09:27:17.105319Z", + "shell.execute_reply": "2024-11-24T09:27:17.104586Z" } }, "outputs": [ { "data": { "text/plain": [ - "{'nBits': 2048,\n", + "{'fpSize': 2048,\n", " 'parallel': False,\n", " 'radius': 3,\n", + " 'safe_inference_mode': False,\n", " 'useBondTypes': True,\n", " 'useChirality': False,\n", " 'useCounts': False,\n", @@ -186,7 +593,7 @@ }, { "cell_type": "markdown", - "id": "45296da6", + "id": "d453fa33", "metadata": {}, "source": [ "The corresponding .set_params() method can be used to update the settings from options or from a dictionary (via ** unpackaging). The get_params and set_params methods are sometimes used by sklearn, as example hyperparameter search objects." @@ -195,13 +602,13 @@ { "cell_type": "code", "execution_count": 6, - "id": "4229d3d3", + "id": "3a27b07a", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:10.523546Z", - "iopub.status.busy": "2024-04-12T12:10:10.523347Z", - "iopub.status.idle": "2024-04-12T12:10:10.527067Z", - "shell.execute_reply": "2024-04-12T12:10:10.526450Z" + "iopub.execute_input": "2024-11-24T09:27:17.107710Z", + "iopub.status.busy": "2024-11-24T09:27:17.107495Z", + "iopub.status.idle": "2024-11-24T09:27:17.111268Z", + "shell.execute_reply": "2024-11-24T09:27:17.110754Z" } }, "outputs": [ @@ -209,20 +616,20 @@ "name": "stdout", "output_type": "stream", "text": [ - "MorganFingerprintTransformer(nBits=256)\n" + "MorganFingerprintTransformer(fpSize=256)\n" ] } ], "source": [ "parameters[\"radius\"] = 2\n", - "parameters[\"nBits\"] = 256\n", + "parameters[\"fpSize\"] = 256\n", "transformer.set_params(**parameters)\n", "print(transformer)" ] }, { "cell_type": "markdown", - "id": "1d38c224", + "id": "3dd372d3", "metadata": {}, "source": [ "Transformation is easy, simply use the .transform() method. For sklearn compatibility the scikit-learn transformers also have a .fit_transform() method, but it is usually not fitting anything." @@ -231,13 +638,13 @@ { "cell_type": "code", "execution_count": 7, - "id": "d2276e30", + "id": "0f141920", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:10.529451Z", - "iopub.status.busy": "2024-04-12T12:10:10.529229Z", - "iopub.status.idle": "2024-04-12T12:10:10.533310Z", - "shell.execute_reply": "2024-04-12T12:10:10.532819Z" + "iopub.execute_input": "2024-11-24T09:27:17.113572Z", + "iopub.status.busy": "2024-11-24T09:27:17.113344Z", + "iopub.status.idle": "2024-11-24T09:27:17.117356Z", + "shell.execute_reply": "2024-11-24T09:27:17.116845Z" } }, "outputs": [ @@ -245,7 +652,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "fps is a with shape (6, 256) and data type int8\n" + "fps is a with shape (6, 256) and data type uint8\n" ] } ], @@ -256,7 +663,7 @@ }, { "cell_type": "markdown", - "id": "666bf64b", + "id": "9cb75226", "metadata": {}, "source": [ "For sklearn compatibility, the transform function can be given a second parameter, usually representing the targets in the machine learning, but it is simply ignored most of the time" @@ -265,13 +672,13 @@ { "cell_type": "code", "execution_count": 8, - "id": "d3b01806", + "id": "481e527f", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:10.535668Z", - "iopub.status.busy": "2024-04-12T12:10:10.535455Z", - "iopub.status.idle": "2024-04-12T12:10:10.540535Z", - "shell.execute_reply": "2024-04-12T12:10:10.539917Z" + "iopub.execute_input": "2024-11-24T09:27:17.119855Z", + "iopub.status.busy": "2024-11-24T09:27:17.119584Z", + "iopub.status.idle": "2024-11-24T09:27:17.124520Z", + "shell.execute_reply": "2024-11-24T09:27:17.124025Z" } }, "outputs": [ @@ -283,7 +690,7 @@ " [1, 0, 0, ..., 0, 0, 0],\n", " [0, 0, 0, ..., 0, 0, 1],\n", " [1, 1, 0, ..., 0, 0, 0],\n", - " [1, 1, 0, ..., 0, 0, 0]], dtype=int8)" + " [1, 1, 0, ..., 0, 0, 0]], dtype=uint8)" ] }, "execution_count": 8, @@ -298,7 +705,7 @@ }, { "cell_type": "markdown", - "id": "1e5c385f", + "id": "500cec09", "metadata": {}, "source": [ "Sometimes we may want to transform SMILES into molecules, and scikit-mol also has a transformer for that. It simply takes a list of SMILES and produces a list of RDKit molecules, this may come in handy when building pipelines for machine learning models, as we will demo in another notebook." @@ -307,13 +714,13 @@ { "cell_type": "code", "execution_count": 9, - "id": "26081bb2", + "id": "7773a5a0", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:10.542876Z", - "iopub.status.busy": "2024-04-12T12:10:10.542661Z", - "iopub.status.idle": "2024-04-12T12:10:10.546656Z", - "shell.execute_reply": "2024-04-12T12:10:10.546143Z" + "iopub.execute_input": "2024-11-24T09:27:17.126934Z", + "iopub.status.busy": "2024-11-24T09:27:17.126713Z", + "iopub.status.idle": "2024-11-24T09:27:17.131063Z", + "shell.execute_reply": "2024-11-24T09:27:17.130539Z" } }, "outputs": [ @@ -327,6 +734,7 @@ ], "source": [ "from scikit_mol.conversions import SmilesToMolTransformer\n", + "\n", "smi2mol = SmilesToMolTransformer()\n", "print(smi2mol)" ] @@ -334,13 +742,13 @@ { "cell_type": "code", "execution_count": 10, - "id": "6b0e5f4a", + "id": "fa484453", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:10.548964Z", - "iopub.status.busy": "2024-04-12T12:10:10.548714Z", - "iopub.status.idle": "2024-04-12T12:10:10.553416Z", - "shell.execute_reply": "2024-04-12T12:10:10.552805Z" + "iopub.execute_input": "2024-11-24T09:27:17.133328Z", + "iopub.status.busy": "2024-11-24T09:27:17.133133Z", + "iopub.status.idle": "2024-11-24T09:27:17.137378Z", + "shell.execute_reply": "2024-11-24T09:27:17.136857Z" } }, "outputs": [ @@ -348,12 +756,12 @@ "name": "stdout", "output_type": "stream", "text": [ - "[[]\n", - " []\n", - " []\n", - " []\n", - " []\n", - " []]\n" + "[[]\n", + " []\n", + " []\n", + " []\n", + " []\n", + " []]\n" ] } ], diff --git a/notebooks/01_basic_usage.py b/notebooks/01_basic_usage.py index d6ca01c..65631a3 100644 --- a/notebooks/01_basic_usage.py +++ b/notebooks/01_basic_usage.py @@ -29,12 +29,14 @@ # %% from rdkit import Chem -smiles_strings = ["C12C([C@@H](OC(C=3C=CC(=CC3)F)C=4C=CC(=CC4)F)CC(N1CCCCCC5=CC=CC=C5)CC2)C(=O)OC", -"O(C1=NC=C2C(CN(CC2=C1)C)C3=CC=C(OC)C=C3)CCCN(CC)CC", -"O=S(=O)(N(CC=1C=CC2=CC=CC=C2C1)[C@@H]3CCNC3)C", -"C1(=C2C(CCCC2O)=NC=3C1=CC=CC3)NCC=4C=CC(=CC4)Cl", -"C1NC[C@@H](C1)[C@H](OC=2C=CC(=NC2C)OC)CC(C)C", -"FC(F)(F)C=1C(CN(C2CCNCC2)CC(CC)CC)=CC=CC1"] +smiles_strings = [ + "C12C([C@@H](OC(C=3C=CC(=CC3)F)C=4C=CC(=CC4)F)CC(N1CCCCCC5=CC=CC=C5)CC2)C(=O)OC", + "O(C1=NC=C2C(CN(CC2=C1)C)C3=CC=C(OC)C=C3)CCCN(CC)CC", + "O=S(=O)(N(CC=1C=CC2=CC=CC=C2C1)[C@@H]3CCNC3)C", + "C1(=C2C(CCCC2O)=NC=3C1=CC=CC3)NCC=4C=CC(=CC4)Cl", + "C1NC[C@@H](C1)[C@H](OC=2C=CC(=NC2C)OC)CC(C)C", + "FC(F)(F)C=1C(CN(C2CCNCC2)CC(CC)CC)=CC=CC1", +] mols = [Chem.MolFromSmiles(smiles) for smiles in smiles_strings] @@ -48,7 +50,7 @@ print(transformer) # %% [markdown] -# It actually renders as a cute little interactive block in the Jupyter notebook and lists the options that are not the default values. If we print it, it also gives the information on the settings. +# It actually renders as a cute little interactive block in the Jupyter notebook and lists the options that are not the default values. If we print it, it also gives the information on the settings. # # ![An image of the interactive transformer widget](images/Transformer_Widget.jpg "Transformer object rendering in Jupyter") # @@ -69,7 +71,7 @@ # %% parameters["radius"] = 2 -parameters["nBits"] = 256 +parameters["fpSize"] = 256 transformer.set_params(**parameters) print(transformer) @@ -92,6 +94,7 @@ # %% from scikit_mol.conversions import SmilesToMolTransformer + smi2mol = SmilesToMolTransformer() print(smi2mol) diff --git a/notebooks/02_descriptor_transformer.ipynb b/notebooks/02_descriptor_transformer.ipynb index 268e235..43b3075 100644 --- a/notebooks/02_descriptor_transformer.ipynb +++ b/notebooks/02_descriptor_transformer.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "80ef57b6", + "id": "e3cf34ca", "metadata": {}, "source": [ "# Desc2DTransformer: RDKit descriptors transformer\n", @@ -13,13 +13,13 @@ { "cell_type": "code", "execution_count": 1, - "id": "51b69a7e", + "id": "81745b1f", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:11.861457Z", - "iopub.status.busy": "2024-04-12T12:10:11.861246Z", - "iopub.status.idle": "2024-04-12T12:10:12.860086Z", - "shell.execute_reply": "2024-04-12T12:10:12.859376Z" + "iopub.execute_input": "2024-11-24T09:27:18.828147Z", + "iopub.status.busy": "2024-11-24T09:27:18.827339Z", + "iopub.status.idle": "2024-11-24T09:27:19.887178Z", + "shell.execute_reply": "2024-11-24T09:27:19.886482Z" }, "lines_to_next_cell": 0 }, @@ -33,7 +33,7 @@ }, { "cell_type": "markdown", - "id": "1e253d9e", + "id": "2293e9e6", "metadata": {}, "source": [ "After instantiation of the descriptor transformer, we can query which descriptors it found available in the RDKit framework." @@ -42,13 +42,13 @@ { "cell_type": "code", "execution_count": 2, - "id": "f1d8fc37", + "id": "dd9a2ad0", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:12.863149Z", - "iopub.status.busy": "2024-04-12T12:10:12.862837Z", - "iopub.status.idle": "2024-04-12T12:10:12.868668Z", - "shell.execute_reply": "2024-04-12T12:10:12.868151Z" + "iopub.execute_input": "2024-11-24T09:27:19.890505Z", + "iopub.status.busy": "2024-11-24T09:27:19.889986Z", + "iopub.status.idle": "2024-11-24T09:27:19.896597Z", + "shell.execute_reply": "2024-11-24T09:27:19.896028Z" } }, "outputs": [ @@ -56,7 +56,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "There are 209 available descriptors\n", + "There are 210 available descriptors\n", "The first five descriptor names: ['MaxAbsEStateIndex', 'MaxEStateIndex', 'MinAbsEStateIndex', 'MinEStateIndex', 'qed']\n" ] } @@ -70,7 +70,7 @@ }, { "cell_type": "markdown", - "id": "5bb186e5", + "id": "110c00c0", "metadata": {}, "source": [ "We can transform molecules to their descriptor profiles" @@ -79,19 +79,31 @@ { "cell_type": "code", "execution_count": 3, - "id": "702168a7", + "id": "4431a910", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:12.871338Z", - "iopub.status.busy": "2024-04-12T12:10:12.870842Z", - "iopub.status.idle": "2024-04-12T12:10:13.031911Z", - "shell.execute_reply": "2024-04-12T12:10:13.031258Z" + "iopub.execute_input": "2024-11-24T09:27:19.899516Z", + "iopub.status.busy": "2024-11-24T09:27:19.899244Z", + "iopub.status.idle": "2024-11-24T09:27:20.125197Z", + "shell.execute_reply": "2024-11-24T09:27:20.123935Z" } }, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:19] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:19] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:19] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:19] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:19] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:19] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAh8AAAGdCAYAAACyzRGfAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAABn50lEQVR4nO3deXwb9Zk/8M/M6PAV27lsx8QJIQ0knC0pDS60peASssDCkm2BZVugbNm2gS4JvbK/BlraEmB3gUJDaPtLA/21lDbsQgtb0gVDwlKSAAHKHRIIJODYIYdvW9d8f3/MoZnRyLakGWnkfN6vl7EtKaORZDSPnuf5PiMJIQSIiIiIikQu9Q4QERHRoYXBBxERERUVgw8iIiIqKgYfREREVFQMPoiIiKioGHwQERFRUTH4ICIioqJi8EFERERFFSr1DjipqoqOjg5MmDABkiSVeneIiIhoDIQQ6OvrQ3NzM2R55NxG4IKPjo4OtLS0lHo3iIiIKA+7d+/G9OnTR7xN4IKPCRMmANB2vra2tsR7Q0RERGPR29uLlpYW8zg+ksAFH0appba2lsEHERFRmRlLywQbTomIiKiocgo+UqkUVqxYgVmzZqGyshKzZ8/GD3/4Q1hPjCuEwHXXXYdp06ahsrISbW1t2L59u+c7TkREROUpp+Dj5ptvxurVq/HTn/4Ub7zxBm6++WbccsstuPPOO83b3HLLLbjjjjtw9913Y8uWLaiursbChQsxPDzs+c4TERFR+ZGENW0xinPOOQeNjY1Ys2aNednixYtRWVmJX//61xBCoLm5Gddeey2++c1vAgB6enrQ2NiIe+65BxdddNGo99Hb24u6ujr09PSw54OIiKhM5HL8zinz8clPfhLt7e146623AAB//etf8fTTT2PRokUAgJ07d6KzsxNtbW3mv6mrq8OCBQuwadOmXB8HERERjUM5rXb57ne/i97eXsydOxeKoiCVSuHHP/4xLrnkEgBAZ2cnAKCxsdH27xobG83rnGKxGGKxmPl7b29vTg+AiIiIyktOmY/f//73+M1vfoP77rsPL7zwAu699178+7//O+699968d2DlypWoq6szvzhgjIiIaHzLKfj41re+he9+97u46KKLcNxxx+GLX/wili5dipUrVwIAmpqaAABdXV22f9fV1WVe57R8+XL09PSYX7t3787ncRAREVGZ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sQWNjz32GGpra3H00UcX54HkqtQdr8Vw//33i2g0Ku655x7x+uuviyuvvFLU19fbOoOpdK699lqxYcMGsXPnTvGXv/xFtLW1iSlTpoi9e/cKIYT46le/KmbMmCGeeOIJ8fzzz4vW1lbR2tpa4r0+dPT19YkXX3xRvPjiiwKAuPXWW8WLL74o3nvvPSGEEDfddJOor68Xf/jDH8TLL78szjvvPDFr1iwxNDRkbuOss84SH/vYx8SWLVvE008/LebMmSMuvvjiUj2kcW2k16uvr09885vfFJs2bRI7d+4Ujz/+uDjxxBPFnDlzxPDwsLkNvl7F87WvfU3U1dWJDRs2iD179phfg4OD5m1Gew9MJpPi2GOPFWeeeaZ46aWXxPr168XUqVPF8uXLS/GQxuSQCD6EEOLOO+8UM2bMEJFIRHziE58QmzdvLvUuke7CCy8U06ZNE5FIRBx22GHiwgsvFDt27DCvHxoaEl//+tfFxIkTRVVVlfi7v/s7sWfPnhLu8aHlySefFAAyvi699FIhhLbcdsWKFaKxsVFEo1FxxhlniG3bttm2sX//fnHxxReLmpoaUVtbKy6//HLR19dXgkcz/o30eg0ODoozzzxTTJ06VYTDYTFz5kzxla98JeODGF+v4nF7rQCItWvXmrcZy3vgu+++KxYtWiQqKyvFlClTxLXXXisSiUSRH83YSUIIUexsCxERER26xn3PBxEREQULgw8iIiIqKgYfREREVFQMPoiIiKioGHwQERFRUTH4ICIioqJi8EFERERFxeCDiIiIiorBBxERERUVgw8iIiIqKgYfREREVFQMPoiIiKio/j+HZKvLSQUNfwAAAABJRU5ErkJggg==", + "image/png": "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", "text/plain": [ "
" ] @@ -110,7 +122,7 @@ }, { "cell_type": "markdown", - "id": "ea6f1325", + "id": "fdcb0698", "metadata": {}, "source": [ "If we only want some of them, this can be specified at object instantiation." @@ -119,13 +131,13 @@ { "cell_type": "code", "execution_count": 4, - "id": "ab72d398", + "id": "6caa9a54", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:13.034545Z", - "iopub.status.busy": "2024-04-12T12:10:13.034315Z", - "iopub.status.idle": "2024-04-12T12:10:13.038330Z", - "shell.execute_reply": "2024-04-12T12:10:13.037823Z" + "iopub.execute_input": "2024-11-24T09:27:20.131216Z", + "iopub.status.busy": "2024-11-24T09:27:20.130932Z", + "iopub.status.idle": "2024-11-24T09:27:20.135202Z", + "shell.execute_reply": "2024-11-24T09:27:20.134644Z" } }, "outputs": [ @@ -145,7 +157,7 @@ }, { "cell_type": "markdown", - "id": "c974320e", + "id": "52eaef77", "metadata": {}, "source": [ "If we want to update the selected descriptors on an already existing object, this can be done via the .set_params() method" @@ -154,13 +166,13 @@ { "cell_type": "code", "execution_count": 5, - "id": "4af5488d", + "id": "78fc5691", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:13.040697Z", - "iopub.status.busy": "2024-04-12T12:10:13.040482Z", - "iopub.status.idle": "2024-04-12T12:10:13.044948Z", - "shell.execute_reply": "2024-04-12T12:10:13.044445Z" + "iopub.execute_input": "2024-11-24T09:27:20.138477Z", + "iopub.status.busy": "2024-11-24T09:27:20.138022Z", + "iopub.status.idle": "2024-11-24T09:27:20.151081Z", + "shell.execute_reply": "2024-11-24T09:27:20.150278Z" } }, "outputs": [ @@ -180,7 +192,7 @@ { "cell_type": "code", "execution_count": null, - "id": "29367a7f", + "id": "4796a16e", "metadata": {}, "outputs": [], "source": [] diff --git a/notebooks/03_example_pipeline.ipynb b/notebooks/03_example_pipeline.ipynb index f2c9fbd..858eaf0 100644 --- a/notebooks/03_example_pipeline.ipynb +++ b/notebooks/03_example_pipeline.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "f3f842d3", + "id": "e7c43298", "metadata": {}, "source": [ "# Pipelining the scikit-mol transformer\n", @@ -15,13 +15,13 @@ { "cell_type": "code", "execution_count": 1, - "id": "b92c5a96", + "id": "79139b10", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:14.474481Z", - "iopub.status.busy": "2024-04-12T12:10:14.474256Z", - "iopub.status.idle": "2024-04-12T12:10:15.156429Z", - "shell.execute_reply": "2024-04-12T12:10:15.155756Z" + "iopub.execute_input": "2024-11-24T09:27:21.863626Z", + "iopub.status.busy": "2024-11-24T09:27:21.863272Z", + "iopub.status.idle": "2024-11-24T09:27:22.718519Z", + "shell.execute_reply": "2024-11-24T09:27:22.717789Z" } }, "outputs": [], @@ -39,13 +39,13 @@ { "cell_type": "code", "execution_count": 2, - "id": "0d79bc45", + "id": "17a9cdd7", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:15.159285Z", - "iopub.status.busy": "2024-04-12T12:10:15.158830Z", - "iopub.status.idle": "2024-04-12T12:10:15.164599Z", - "shell.execute_reply": "2024-04-12T12:10:15.163957Z" + "iopub.execute_input": "2024-11-24T09:27:22.722219Z", + "iopub.status.busy": "2024-11-24T09:27:22.721369Z", + "iopub.status.idle": "2024-11-24T09:27:22.727326Z", + "shell.execute_reply": "2024-11-24T09:27:22.726709Z" }, "lines_to_next_cell": 0 }, @@ -57,7 +57,7 @@ }, { "cell_type": "markdown", - "id": "3569affd", + "id": "066131b8", "metadata": {}, "source": [ "The dataset is a subset of the SLC6A4 actives from ExcapeDB. They are hand selected to give test set performance despite the small size, and are provided as example data only and should not be used to build serious QSAR models.\n", @@ -68,13 +68,13 @@ { "cell_type": "code", "execution_count": 3, - "id": "594f45ba", + "id": "a3ec0a23", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:15.167046Z", - "iopub.status.busy": "2024-04-12T12:10:15.166800Z", - "iopub.status.idle": "2024-04-12T12:10:15.202826Z", - "shell.execute_reply": "2024-04-12T12:10:15.202180Z" + "iopub.execute_input": "2024-11-24T09:27:22.729951Z", + "iopub.status.busy": "2024-11-24T09:27:22.729732Z", + "iopub.status.idle": "2024-11-24T09:27:22.769704Z", + "shell.execute_reply": "2024-11-24T09:27:22.768854Z" }, "lines_to_next_cell": 0 }, @@ -94,7 +94,7 @@ }, { "cell_type": "markdown", - "id": "04af16b2", + "id": "eccaf4af", "metadata": {}, "source": [ "Then, let's import some tools from scikit-learn and two transformers from scikit-mol" @@ -103,13 +103,13 @@ { "cell_type": "code", "execution_count": 4, - "id": "ed19f736", + "id": "4eb8f0fa", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:15.205344Z", - "iopub.status.busy": "2024-04-12T12:10:15.205135Z", - "iopub.status.idle": "2024-04-12T12:10:15.593454Z", - "shell.execute_reply": "2024-04-12T12:10:15.592778Z" + "iopub.execute_input": "2024-11-24T09:27:22.772861Z", + "iopub.status.busy": "2024-11-24T09:27:22.772534Z", + "iopub.status.idle": "2024-11-24T09:27:23.182612Z", + "shell.execute_reply": "2024-11-24T09:27:23.181966Z" }, "lines_to_next_cell": 0 }, @@ -125,13 +125,13 @@ { "cell_type": "code", "execution_count": 5, - "id": "c4a255f4", + "id": "99edec0f", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:15.596462Z", - "iopub.status.busy": "2024-04-12T12:10:15.595952Z", - "iopub.status.idle": "2024-04-12T12:10:15.601122Z", - "shell.execute_reply": "2024-04-12T12:10:15.600608Z" + "iopub.execute_input": "2024-11-24T09:27:23.185612Z", + "iopub.status.busy": "2024-11-24T09:27:23.185269Z", + "iopub.status.idle": "2024-11-24T09:27:23.190844Z", + "shell.execute_reply": "2024-11-24T09:27:23.190290Z" } }, "outputs": [], @@ -141,7 +141,7 @@ }, { "cell_type": "markdown", - "id": "a088665f", + "id": "b8380817", "metadata": {}, "source": [ "After a split into train and test, we'll build the first pipeline" @@ -150,13 +150,13 @@ { "cell_type": "code", "execution_count": 6, - "id": "0ddbf668", + "id": "a27d6ff9", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:15.603558Z", - "iopub.status.busy": "2024-04-12T12:10:15.603329Z", - "iopub.status.idle": "2024-04-12T12:10:15.608527Z", - "shell.execute_reply": "2024-04-12T12:10:15.608014Z" + "iopub.execute_input": "2024-11-24T09:27:23.193426Z", + "iopub.status.busy": "2024-11-24T09:27:23.193188Z", + "iopub.status.idle": "2024-11-24T09:27:23.198881Z", + "shell.execute_reply": "2024-11-24T09:27:23.198225Z" } }, "outputs": [ @@ -176,7 +176,7 @@ }, { "cell_type": "markdown", - "id": "004b0d25", + "id": "6c12f9a8", "metadata": {}, "source": [ "We can do the fit by simply providing the list of RDKit molecule objects" @@ -185,13 +185,13 @@ { "cell_type": "code", "execution_count": 7, - "id": "231d0534", + "id": "634ca919", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:15.610826Z", - "iopub.status.busy": "2024-04-12T12:10:15.610623Z", - "iopub.status.idle": "2024-04-12T12:10:15.735973Z", - "shell.execute_reply": "2024-04-12T12:10:15.735308Z" + "iopub.execute_input": "2024-11-24T09:27:23.201230Z", + "iopub.status.busy": "2024-11-24T09:27:23.201013Z", + "iopub.status.idle": "2024-11-24T09:27:23.265644Z", + "shell.execute_reply": "2024-11-24T09:27:23.264698Z" }, "lines_to_next_cell": 0 }, @@ -213,7 +213,7 @@ }, { "cell_type": "markdown", - "id": "55915786", + "id": "8440cc5a", "metadata": {}, "source": [ "Nevermind the performance, or the exact value of the prediction, this is for demonstration purpures. We can easily predict on lists of molecules" @@ -222,13 +222,13 @@ { "cell_type": "code", "execution_count": 8, - "id": "5d1e9220", + "id": "f4431aab", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:15.738365Z", - "iopub.status.busy": "2024-04-12T12:10:15.738132Z", - "iopub.status.idle": "2024-04-12T12:10:15.744818Z", - "shell.execute_reply": "2024-04-12T12:10:15.744279Z" + "iopub.execute_input": "2024-11-24T09:27:23.269015Z", + "iopub.status.busy": "2024-11-24T09:27:23.268218Z", + "iopub.status.idle": "2024-11-24T09:27:23.280889Z", + "shell.execute_reply": "2024-11-24T09:27:23.279967Z" } }, "outputs": [ @@ -249,7 +249,7 @@ }, { "cell_type": "markdown", - "id": "07cf53ea", + "id": "a60e242b", "metadata": {}, "source": [ "We can also expand the already fitted pipeline, how about creating a pipeline that can predict directly from SMILES? With scikit-mol that is easy!" @@ -258,13 +258,13 @@ { "cell_type": "code", "execution_count": 9, - "id": "eb8ce486", + "id": "a908097d", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:15.747499Z", - "iopub.status.busy": "2024-04-12T12:10:15.746917Z", - "iopub.status.idle": "2024-04-12T12:10:15.754613Z", - "shell.execute_reply": "2024-04-12T12:10:15.754088Z" + "iopub.execute_input": "2024-11-24T09:27:23.284650Z", + "iopub.status.busy": "2024-11-24T09:27:23.283862Z", + "iopub.status.idle": "2024-11-24T09:27:23.298454Z", + "shell.execute_reply": "2024-11-24T09:27:23.297546Z" } }, "outputs": [ @@ -288,13 +288,13 @@ { "cell_type": "code", "execution_count": 10, - "id": "1444b605", + "id": "0124653c", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:15.757246Z", - "iopub.status.busy": "2024-04-12T12:10:15.756914Z", - "iopub.status.idle": "2024-04-12T12:10:15.761416Z", - "shell.execute_reply": "2024-04-12T12:10:15.760858Z" + "iopub.execute_input": "2024-11-24T09:27:23.302185Z", + "iopub.status.busy": "2024-11-24T09:27:23.301318Z", + "iopub.status.idle": "2024-11-24T09:27:23.307070Z", + "shell.execute_reply": "2024-11-24T09:27:23.306539Z" } }, "outputs": [ @@ -315,7 +315,7 @@ }, { "cell_type": "markdown", - "id": "90d7817b", + "id": "069e2d01", "metadata": {}, "source": [ "From here, the pipelines could be pickled, and later loaded for easy prediction on RDKit molecule objects or SMILES in other scripts. The transformation with the MorganTransformer will be the same as during fitting, so no need to remember if radius 2 or 3 was used for this or that model, as it is already in the pipeline itself. If we need to see the parameters for a particular pipeline of model, we can always get the non default settings via print or all settings with .get_params()." @@ -324,13 +324,13 @@ { "cell_type": "code", "execution_count": 11, - "id": "1eacda8f", + "id": "63c8ef60", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:15.765023Z", - "iopub.status.busy": "2024-04-12T12:10:15.764682Z", - "iopub.status.idle": "2024-04-12T12:10:15.772249Z", - "shell.execute_reply": "2024-04-12T12:10:15.771692Z" + "iopub.execute_input": "2024-11-24T09:27:23.309849Z", + "iopub.status.busy": "2024-11-24T09:27:23.309649Z", + "iopub.status.idle": "2024-11-24T09:27:23.317613Z", + "shell.execute_reply": "2024-11-24T09:27:23.316837Z" }, "lines_to_next_cell": 2 }, @@ -348,15 +348,17 @@ " 'pipe': Pipeline(steps=[('mol_transformer', MorganFingerprintTransformer()),\n", " ('Regressor', Ridge())]),\n", " 'smiles_transformer__parallel': False,\n", + " 'smiles_transformer__safe_inference_mode': False,\n", " 'pipe__memory': None,\n", " 'pipe__steps': [('mol_transformer', MorganFingerprintTransformer()),\n", " ('Regressor', Ridge())],\n", " 'pipe__verbose': False,\n", " 'pipe__mol_transformer': MorganFingerprintTransformer(),\n", " 'pipe__Regressor': Ridge(),\n", - " 'pipe__mol_transformer__nBits': 2048,\n", + " 'pipe__mol_transformer__fpSize': 2048,\n", " 'pipe__mol_transformer__parallel': False,\n", " 'pipe__mol_transformer__radius': 2,\n", + " 'pipe__mol_transformer__safe_inference_mode': False,\n", " 'pipe__mol_transformer__useBondTypes': True,\n", " 'pipe__mol_transformer__useChirality': False,\n", " 'pipe__mol_transformer__useCounts': False,\n", diff --git a/notebooks/04_standardizer.ipynb b/notebooks/04_standardizer.ipynb index 1cf8175..ea59112 100644 --- a/notebooks/04_standardizer.ipynb +++ b/notebooks/04_standardizer.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "be682e9c", + "id": "095e3de9", "metadata": {}, "source": [ "# Molecule standardization\n", @@ -12,13 +12,13 @@ { "cell_type": "code", "execution_count": 1, - "id": "2aa91923", + "id": "d40bdabe", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:17.122184Z", - "iopub.status.busy": "2024-04-12T12:10:17.121928Z", - "iopub.status.idle": "2024-04-12T12:10:17.878455Z", - "shell.execute_reply": "2024-04-12T12:10:17.877770Z" + "iopub.execute_input": "2024-11-24T09:27:25.092168Z", + "iopub.status.busy": "2024-11-24T09:27:25.091775Z", + "iopub.status.idle": "2024-11-24T09:27:25.972589Z", + "shell.execute_reply": "2024-11-24T09:27:25.971827Z" } }, "outputs": [], @@ -33,7 +33,7 @@ }, { "cell_type": "markdown", - "id": "0fed8d0b", + "id": "1f739296", "metadata": {}, "source": [ "For demonstration let's create some molecules with different protonation states. The two first molecules are Benzoic acid and Sodium benzoate." @@ -42,20 +42,20 @@ { "cell_type": "code", "execution_count": 2, - "id": "934c031b", + "id": "5a45dfd5", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:17.881422Z", - "iopub.status.busy": "2024-04-12T12:10:17.881092Z", - "iopub.status.idle": "2024-04-12T12:10:17.889228Z", - "shell.execute_reply": "2024-04-12T12:10:17.888702Z" + "iopub.execute_input": "2024-11-24T09:27:25.975743Z", + "iopub.status.busy": "2024-11-24T09:27:25.975328Z", + "iopub.status.idle": "2024-11-24T09:27:25.984915Z", + "shell.execute_reply": "2024-11-24T09:27:25.984323Z" } }, "outputs": [ { "data": { "text/plain": [ - "array([], dtype=object)" + "array([], dtype=object)" ] }, "metadata": {}, @@ -64,7 +64,7 @@ { "data": { "text/plain": [ - "array([], dtype=object)" + "array([], dtype=object)" ] }, "metadata": {}, @@ -84,7 +84,7 @@ }, { "cell_type": "markdown", - "id": "e68b0eff", + "id": "1974e56a", "metadata": {}, "source": [ "We can simply use the transformer directly and get a list of standardized molecules" @@ -93,13 +93,13 @@ { "cell_type": "code", "execution_count": 3, - "id": "c18bbd3e", + "id": "d13141c6", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:17.891559Z", - "iopub.status.busy": "2024-04-12T12:10:17.891342Z", - "iopub.status.idle": "2024-04-12T12:10:17.906792Z", - "shell.execute_reply": "2024-04-12T12:10:17.906295Z" + "iopub.execute_input": "2024-11-24T09:27:25.987910Z", + "iopub.status.busy": "2024-11-24T09:27:25.987688Z", + "iopub.status.idle": "2024-11-24T09:27:26.003398Z", + "shell.execute_reply": "2024-11-24T09:27:26.002776Z" } }, "outputs": [ @@ -129,7 +129,7 @@ }, { "cell_type": "markdown", - "id": "da3fc3e2", + "id": "d268d331", "metadata": {}, "source": [ "Some of the molecules were desalted and neutralized.\n", @@ -140,13 +140,13 @@ { "cell_type": "code", "execution_count": 4, - "id": "d03be7dc", + "id": "a376a759", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:17.909344Z", - "iopub.status.busy": "2024-04-12T12:10:17.909132Z", - "iopub.status.idle": "2024-04-12T12:10:17.939678Z", - "shell.execute_reply": "2024-04-12T12:10:17.939104Z" + "iopub.execute_input": "2024-11-24T09:27:26.006132Z", + "iopub.status.busy": "2024-11-24T09:27:26.005882Z", + "iopub.status.idle": "2024-11-24T09:27:26.034347Z", + "shell.execute_reply": "2024-11-24T09:27:26.033821Z" }, "lines_to_next_cell": 2 }, @@ -158,30 +158,6 @@ "Predictions with no standardization: [0.51983795 0.61543701 2.31738354 3.01206795 3.44085399 4.37516731]\n", "Predictions with standardization: [0.51983795 0.51983795 2.06562022 3.01206795 3.95446692 4.92816899]\n" ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n" - ] } ], "source": [ @@ -202,7 +178,7 @@ }, { "cell_type": "markdown", - "id": "9cfab56c", + "id": "f0d071fb", "metadata": {}, "source": [ "As we can see, the predictions with the standardizer and without are different. The two first molecules were benzoic acid and sodium benzoate, which with the standardized pipeline is predicted as the same, but differently with the nonstandardized pipeline. Wheter we want to make the prediction on the parent compound, or predict the exact form, will of course depend on the use-case, but now there is at least a way to handle it easily in pipelined predictors.\n", @@ -215,13 +191,13 @@ { "cell_type": "code", "execution_count": 5, - "id": "e5f8495f", + "id": "50f71bca", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:17.942493Z", - "iopub.status.busy": "2024-04-12T12:10:17.942248Z", - "iopub.status.idle": "2024-04-12T12:10:17.961792Z", - "shell.execute_reply": "2024-04-12T12:10:17.961206Z" + "iopub.execute_input": "2024-11-24T09:27:26.037572Z", + "iopub.status.busy": "2024-11-24T09:27:26.036950Z", + "iopub.status.idle": "2024-11-24T09:27:26.056194Z", + "shell.execute_reply": "2024-11-24T09:27:26.055013Z" } }, "outputs": [ @@ -232,30 +208,6 @@ "Predictions with no standardization: [0.07445775 0.96053374 2.05993278 3.00857908 3.96365443 4.93284221]\n", "Predictions with standardization: [0.07445775 0.07445775 2.32132164 3.00857908 2.68502208 4.30275549]\n" ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n", - "[19:07:06] DEPRECATION WARNING: please use MorganGenerator\n" - ] } ], "source": [ diff --git a/notebooks/05_smiles_sanitaztion.ipynb b/notebooks/05_smiles_sanitaztion.ipynb index 70eed7e..d59c830 100644 --- a/notebooks/05_smiles_sanitaztion.ipynb +++ b/notebooks/05_smiles_sanitaztion.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "b6fe4dbd", + "id": "9b787560", "metadata": {}, "source": [ "# SMILES sanitation\n", @@ -12,13 +12,13 @@ { "cell_type": "code", "execution_count": 1, - "id": "c46f479f", + "id": "612aa974", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:19.504461Z", - "iopub.status.busy": "2024-04-12T12:10:19.504268Z", - "iopub.status.idle": "2024-04-12T12:10:19.893131Z", - "shell.execute_reply": "2024-04-12T12:10:19.892459Z" + "iopub.execute_input": "2024-11-24T09:27:27.545695Z", + "iopub.status.busy": "2024-11-24T09:27:27.545293Z", + "iopub.status.idle": "2024-11-24T09:27:28.079174Z", + "shell.execute_reply": "2024-11-24T09:27:28.078490Z" } }, "outputs": [], @@ -33,7 +33,7 @@ }, { "cell_type": "markdown", - "id": "5db46e76", + "id": "0f957a69", "metadata": {}, "source": [ "Now, this example dataset contain all sanitizable SMILES, so for demonstration purposes, we will corrupt one of them" @@ -42,13 +42,13 @@ { "cell_type": "code", "execution_count": 2, - "id": "a7bc5ff6", + "id": "b09cfd6b", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:19.896100Z", - "iopub.status.busy": "2024-04-12T12:10:19.895854Z", - "iopub.status.idle": "2024-04-12T12:10:19.899441Z", - "shell.execute_reply": "2024-04-12T12:10:19.898958Z" + "iopub.execute_input": "2024-11-24T09:27:28.082222Z", + "iopub.status.busy": "2024-11-24T09:27:28.081921Z", + "iopub.status.idle": "2024-11-24T09:27:28.086003Z", + "shell.execute_reply": "2024-11-24T09:27:28.085450Z" } }, "outputs": [], @@ -59,13 +59,13 @@ { "cell_type": "code", "execution_count": 3, - "id": "a792f370", + "id": "e20fb5cc", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:19.901832Z", - "iopub.status.busy": "2024-04-12T12:10:19.901618Z", - "iopub.status.idle": "2024-04-12T12:10:19.939335Z", - "shell.execute_reply": "2024-04-12T12:10:19.938810Z" + "iopub.execute_input": "2024-11-24T09:27:28.088449Z", + "iopub.status.busy": "2024-11-24T09:27:28.088211Z", + "iopub.status.idle": "2024-11-24T09:27:28.130818Z", + "shell.execute_reply": "2024-11-24T09:27:28.130102Z" }, "lines_to_next_cell": 2 }, @@ -81,7 +81,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "[14:10:19] Explicit valence for atom # 1 N, 4, is greater than permitted\n" + "[10:27:28] Explicit valence for atom # 1 N, 4, is greater than permitted\n" ] } ], @@ -93,7 +93,7 @@ }, { "cell_type": "markdown", - "id": "54341ece", + "id": "f8dccd93", "metadata": {}, "source": [ "If we use these SMILES for the scikit-learn pipeline, we would face an error, so we need to check and clean the dataset first. The CheckSmilesSanitation can help us with that." @@ -102,13 +102,13 @@ { "cell_type": "code", "execution_count": 4, - "id": "849b643f", + "id": "3dbd50b3", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:19.942039Z", - "iopub.status.busy": "2024-04-12T12:10:19.941833Z", - "iopub.status.idle": "2024-04-12T12:10:19.981190Z", - "shell.execute_reply": "2024-04-12T12:10:19.980569Z" + "iopub.execute_input": "2024-11-24T09:27:28.133745Z", + "iopub.status.busy": "2024-11-24T09:27:28.133507Z", + "iopub.status.idle": "2024-11-24T09:27:28.508377Z", + "shell.execute_reply": "2024-11-24T09:27:28.507130Z" } }, "outputs": [ @@ -123,7 +123,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "[14:10:19] Explicit valence for atom # 1 N, 4, is greater than permitted\n" + "[10:27:28] Explicit valence for atom # 1 N, 4, is greater than permitted\n" ] } ], @@ -136,7 +136,7 @@ }, { "cell_type": "markdown", - "id": "5e410e4c", + "id": "c888d7da", "metadata": {}, "source": [ "Now the smiles_list_valid should be all valid and the y_values filtered as well. Errors are returned, but also accesible after the call to .sanitize() in the .errors property" @@ -145,13 +145,13 @@ { "cell_type": "code", "execution_count": 5, - "id": "2145530c", + "id": "5af5ea3d", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:19.983672Z", - "iopub.status.busy": "2024-04-12T12:10:19.983440Z", - "iopub.status.idle": "2024-04-12T12:10:19.995617Z", - "shell.execute_reply": "2024-04-12T12:10:19.995060Z" + "iopub.execute_input": "2024-11-24T09:27:28.511261Z", + "iopub.status.busy": "2024-11-24T09:27:28.510945Z", + "iopub.status.idle": "2024-11-24T09:27:28.522024Z", + "shell.execute_reply": "2024-11-24T09:27:28.521232Z" } }, "outputs": [ @@ -206,7 +206,7 @@ }, { "cell_type": "markdown", - "id": "c9906a45", + "id": "c2ce2677", "metadata": {}, "source": [ "The checker can also be used only on X" @@ -215,13 +215,13 @@ { "cell_type": "code", "execution_count": 6, - "id": "d5fd425e", + "id": "84db07cc", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:19.998113Z", - "iopub.status.busy": "2024-04-12T12:10:19.997903Z", - "iopub.status.idle": "2024-04-12T12:10:20.040005Z", - "shell.execute_reply": "2024-04-12T12:10:20.039381Z" + "iopub.execute_input": "2024-11-24T09:27:28.524982Z", + "iopub.status.busy": "2024-11-24T09:27:28.524717Z", + "iopub.status.idle": "2024-11-24T09:27:28.569119Z", + "shell.execute_reply": "2024-11-24T09:27:28.568473Z" } }, "outputs": [ @@ -236,7 +236,7 @@ "name": "stderr", "output_type": "stream", "text": [ - "[14:10:19] Explicit valence for atom # 1 N, 4, is greater than permitted\n" + "[10:27:28] Explicit valence for atom # 1 N, 4, is greater than permitted\n" ] }, { diff --git a/notebooks/06_hyperparameter_tuning.ipynb b/notebooks/06_hyperparameter_tuning.ipynb index d15d9fe..9afcc21 100644 --- a/notebooks/06_hyperparameter_tuning.ipynb +++ b/notebooks/06_hyperparameter_tuning.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "9d600dd1", + "id": "f0b0cc54", "metadata": {}, "source": [ "# Full example: Hyperparameter tuning\n", @@ -13,13 +13,13 @@ { "cell_type": "code", "execution_count": 1, - "id": "7df4793c", + "id": "51aa3d62", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:21.479028Z", - "iopub.status.busy": "2024-04-12T12:10:21.478831Z", - "iopub.status.idle": "2024-04-12T12:10:22.543557Z", - "shell.execute_reply": "2024-04-12T12:10:22.542929Z" + "iopub.execute_input": "2024-11-24T09:27:30.230310Z", + "iopub.status.busy": "2024-11-24T09:27:30.230076Z", + "iopub.status.idle": "2024-11-24T09:27:31.452867Z", + "shell.execute_reply": "2024-11-24T09:27:31.452127Z" } }, "outputs": [], @@ -41,7 +41,7 @@ }, { "cell_type": "markdown", - "id": "8acee1c3", + "id": "e07990d0", "metadata": {}, "source": [ "We will need some data. There is a dataset with the SLC6A4 active compounds from ExcapeDB on Zenodo. The scikit-mol project uses a subset of this for testing, and the samples there has been specially selected to give good results in testing (it should therefore be used for any production modelling). If full_set is false, the fast subset will be used, and otherwise the full dataset will be downloaded if needed." @@ -50,13 +50,13 @@ { "cell_type": "code", "execution_count": 2, - "id": "45a8ebf1", + "id": "adbc1868", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:22.546506Z", - "iopub.status.busy": "2024-04-12T12:10:22.546207Z", - "iopub.status.idle": "2024-04-12T12:10:22.550011Z", - "shell.execute_reply": "2024-04-12T12:10:22.549526Z" + "iopub.execute_input": "2024-11-24T09:27:31.455770Z", + "iopub.status.busy": "2024-11-24T09:27:31.455436Z", + "iopub.status.idle": "2024-11-24T09:27:31.459245Z", + "shell.execute_reply": "2024-11-24T09:27:31.458654Z" } }, "outputs": [], @@ -67,15 +67,16 @@ " csv_file = \"SLC6A4_active_excape_export.csv\"\n", " if not os.path.exists(csv_file):\n", " import urllib.request\n", + "\n", " url = \"https://ndownloader.figshare.com/files/25747817\"\n", " urllib.request.urlretrieve(url, csv_file)\n", "else:\n", - " csv_file = '../tests/data/SLC6A4_active_excapedb_subset.csv'" + " csv_file = \"../tests/data/SLC6A4_active_excapedb_subset.csv\"" ] }, { "cell_type": "markdown", - "id": "f3c108d4", + "id": "d2ce3c7f", "metadata": {}, "source": [ "The CSV data is loaded into a Pandas dataframe and the PandasTools utility from RDKit is used to add a column with RDKit molecules" @@ -84,13 +85,13 @@ { "cell_type": "code", "execution_count": 3, - "id": "08c233a7", + "id": "9a283f12", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:22.552652Z", - "iopub.status.busy": "2024-04-12T12:10:22.552186Z", - "iopub.status.idle": "2024-04-12T12:10:22.591597Z", - "shell.execute_reply": "2024-04-12T12:10:22.591017Z" + "iopub.execute_input": "2024-11-24T09:27:31.461622Z", + "iopub.status.busy": "2024-11-24T09:27:31.461384Z", + "iopub.status.idle": "2024-11-24T09:27:31.500359Z", + "shell.execute_reply": "2024-11-24T09:27:31.499764Z" } }, "outputs": [ @@ -112,7 +113,7 @@ }, { "cell_type": "markdown", - "id": "e1828bee", + "id": "e245e989", "metadata": {}, "source": [ "We use the train_test_split to, well, split the dataframe's molecule columns and pXC50 column into lists for train and testing" @@ -121,25 +122,27 @@ { "cell_type": "code", "execution_count": 4, - "id": "5363d05a", + "id": "303b83de", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:22.594077Z", - "iopub.status.busy": "2024-04-12T12:10:22.593866Z", - "iopub.status.idle": "2024-04-12T12:10:22.598774Z", - "shell.execute_reply": "2024-04-12T12:10:22.598212Z" + "iopub.execute_input": "2024-11-24T09:27:31.502982Z", + "iopub.status.busy": "2024-11-24T09:27:31.502779Z", + "iopub.status.idle": "2024-11-24T09:27:31.507447Z", + "shell.execute_reply": "2024-11-24T09:27:31.506962Z" }, "lines_to_next_cell": 2 }, "outputs": [], "source": [ "\n", - "mol_list_train, mol_list_test, y_train, y_test = train_test_split(data.ROMol, data.pXC50, random_state=42)" + "mol_list_train, mol_list_test, y_train, y_test = train_test_split(\n", + " data.ROMol, data.pXC50, random_state=42\n", + ")" ] }, { "cell_type": "markdown", - "id": "bf9e8c8d", + "id": "56247c3b", "metadata": {}, "source": [ "We will standardize the molecules before modelling. This is best done before the hyperparameter optimizatiion of the featurization with the scikit-mol transformer and regression modelling, as the standardization is otherwise done for every loop in the hyperparameter optimization, which will make it take longer time." @@ -148,18 +151,18 @@ { "cell_type": "code", "execution_count": 5, - "id": "885daf12", + "id": "1383d0fc", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:22.601148Z", - "iopub.status.busy": "2024-04-12T12:10:22.600938Z", - "iopub.status.idle": "2024-04-12T12:10:22.967869Z", - "shell.execute_reply": "2024-04-12T12:10:22.967178Z" + "iopub.execute_input": "2024-11-24T09:27:31.509953Z", + "iopub.status.busy": "2024-11-24T09:27:31.509731Z", + "iopub.status.idle": "2024-11-24T09:27:31.830576Z", + "shell.execute_reply": "2024-11-24T09:27:31.829874Z" } }, "outputs": [], "source": [ - "# Probably the recommended way would be to prestandardize the data if there's no changes to the transformer, \n", + "# Probably the recommended way would be to prestandardize the data if there's no changes to the transformer,\n", "# and then add the standardizer in the inference pipeline.\n", "\n", "from scikit_mol.standardizer import Standardizer\n", @@ -170,7 +173,7 @@ }, { "cell_type": "markdown", - "id": "a81ae4c4", + "id": "0775d395", "metadata": {}, "source": [ "A simple pipeline with a MorganTransformer and a Ridge() regression for demonstration." @@ -179,14 +182,15 @@ { "cell_type": "code", "execution_count": 6, - "id": "8fd14250", + "id": "51c74711", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:22.970766Z", - "iopub.status.busy": "2024-04-12T12:10:22.970539Z", - "iopub.status.idle": "2024-04-12T12:10:22.973715Z", - "shell.execute_reply": "2024-04-12T12:10:22.973234Z" - } + "iopub.execute_input": "2024-11-24T09:27:31.833379Z", + "iopub.status.busy": "2024-11-24T09:27:31.833155Z", + "iopub.status.idle": "2024-11-24T09:27:31.836541Z", + "shell.execute_reply": "2024-11-24T09:27:31.835939Z" + }, + "lines_to_next_cell": 2 }, "outputs": [], "source": [ @@ -194,13 +198,12 @@ "moltransformer = MorganFingerprintTransformer()\n", "regressor = Ridge()\n", "\n", - "optimization_pipe = make_pipeline(moltransformer, regressor)\n", - "\n" + "optimization_pipe = make_pipeline(moltransformer, regressor)" ] }, { "cell_type": "markdown", - "id": "ad2752d0", + "id": "8221a682", "metadata": {}, "source": [ "For hyperparameter optimization we import the RandomizedSearchCV class from Scikit-Learn. It will try different random combinations of settings and use internal cross-validation to find the best model. In the end, it will fit the best found parameters on the full set. We also import loguniform, to get a better sampling of some of the parameters." @@ -209,26 +212,27 @@ { "cell_type": "code", "execution_count": 7, - "id": "fa082078", + "id": "4c6b833f", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:22.976061Z", - "iopub.status.busy": "2024-04-12T12:10:22.975846Z", - "iopub.status.idle": "2024-04-12T12:10:22.978721Z", - "shell.execute_reply": "2024-04-12T12:10:22.978155Z" + "iopub.execute_input": "2024-11-24T09:27:31.838854Z", + "iopub.status.busy": "2024-11-24T09:27:31.838668Z", + "iopub.status.idle": "2024-11-24T09:27:31.841636Z", + "shell.execute_reply": "2024-11-24T09:27:31.841130Z" }, "title": "Now hyperparameter tuning" }, "outputs": [], "source": [ "from sklearn.model_selection import RandomizedSearchCV\n", - "#from sklearn.utils.fixes import loguniform\n", + "\n", + "# from sklearn.utils.fixes import loguniform\n", "from scipy.stats import loguniform" ] }, { "cell_type": "markdown", - "id": "fa2a316a", + "id": "6b9d4576", "metadata": {}, "source": [ "With the pipelines, getting the names of the parameters to tune is a bit more tricky, as they are concatenations of the name of the step and the parameter with double underscores in between. We can get the available parameters from the pipeline with the get_params() method, and select the parameters we want to change from there." @@ -237,13 +241,13 @@ { "cell_type": "code", "execution_count": 8, - "id": "046e24d3", + "id": "0af1003b", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:22.981002Z", - "iopub.status.busy": "2024-04-12T12:10:22.980797Z", - "iopub.status.idle": "2024-04-12T12:10:22.986820Z", - "shell.execute_reply": "2024-04-12T12:10:22.986344Z" + "iopub.execute_input": "2024-11-24T09:27:31.843922Z", + "iopub.status.busy": "2024-11-24T09:27:31.843728Z", + "iopub.status.idle": "2024-11-24T09:27:31.849777Z", + "shell.execute_reply": "2024-11-24T09:27:31.849273Z" }, "title": "Which keys do we have?" }, @@ -251,7 +255,7 @@ { "data": { "text/plain": [ - "dict_keys(['memory', 'steps', 'verbose', 'morganfingerprinttransformer', 'ridge', 'morganfingerprinttransformer__nBits', 'morganfingerprinttransformer__parallel', 'morganfingerprinttransformer__radius', 'morganfingerprinttransformer__useBondTypes', 'morganfingerprinttransformer__useChirality', 'morganfingerprinttransformer__useCounts', 'morganfingerprinttransformer__useFeatures', 'ridge__alpha', 'ridge__copy_X', 'ridge__fit_intercept', 'ridge__max_iter', 'ridge__positive', 'ridge__random_state', 'ridge__solver', 'ridge__tol'])" + "dict_keys(['memory', 'steps', 'verbose', 'morganfingerprinttransformer', 'ridge', 'morganfingerprinttransformer__fpSize', 'morganfingerprinttransformer__parallel', 'morganfingerprinttransformer__radius', 'morganfingerprinttransformer__safe_inference_mode', 'morganfingerprinttransformer__useBondTypes', 'morganfingerprinttransformer__useChirality', 'morganfingerprinttransformer__useCounts', 'morganfingerprinttransformer__useFeatures', 'ridge__alpha', 'ridge__copy_X', 'ridge__fit_intercept', 'ridge__max_iter', 'ridge__positive', 'ridge__random_state', 'ridge__solver', 'ridge__tol'])" ] }, "execution_count": 8, @@ -266,7 +270,7 @@ }, { "cell_type": "markdown", - "id": "cd7c2297", + "id": "cb0db6a5", "metadata": {}, "source": [ "We will tune the regularization strength of the Ridge regressor, and try out different parameters for the Morgan fingerprint, namely the number of bits, the radius of the fingerprint, wheter to use counts or bits and features." @@ -275,30 +279,33 @@ { "cell_type": "code", "execution_count": 9, - "id": "cf2c45d7", + "id": "c2d541b3", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:22.989460Z", - "iopub.status.busy": "2024-04-12T12:10:22.988970Z", - "iopub.status.idle": "2024-04-12T12:10:22.993117Z", - "shell.execute_reply": "2024-04-12T12:10:22.992622Z" - }, - "lines_to_next_cell": 1 + "iopub.execute_input": "2024-11-24T09:27:31.852166Z", + "iopub.status.busy": "2024-11-24T09:27:31.851946Z", + "iopub.status.idle": "2024-11-24T09:27:31.856126Z", + "shell.execute_reply": "2024-11-24T09:27:31.855622Z" + } }, "outputs": [], "source": [ "\n", - "param_dist = {'ridge__alpha': loguniform(1e-2, 1e3),\n", - " \"morganfingerprinttransformer__nBits\": [256,512,1024,2048,4096],\n", - " 'morganfingerprinttransformer__radius':[1,2,3,4],\n", - " 'morganfingerprinttransformer__useCounts': [True,False],\n", - " 'morganfingerprinttransformer__useFeatures':[True,False]}" + "param_dist = {\n", + " \"ridge__alpha\": loguniform(1e-2, 1e3),\n", + " \"morganfingerprinttransformer__fpSize\": [256, 512, 1024, 2048, 4096],\n", + " \"morganfingerprinttransformer__radius\": [1, 2, 3, 4],\n", + " \"morganfingerprinttransformer__useCounts\": [True, False],\n", + " \"morganfingerprinttransformer__useFeatures\": [True, False],\n", + "}" ] }, { "cell_type": "markdown", - "id": "d61fc18c", - "metadata": {}, + "id": "2157d154", + "metadata": { + "lines_to_next_cell": 2 + }, "source": [ "The report function was taken from [this example](https://scikit-learn.org/stable/auto_examples/model_selection/plot_randomized_search.html#sphx-glr-auto-examples-model-selection-plot-randomized-search-py) from the scikit learn documentation." ] @@ -306,13 +313,13 @@ { "cell_type": "code", "execution_count": 10, - "id": "fbb2cacd", + "id": "f2c91783", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:22.995477Z", - "iopub.status.busy": "2024-04-12T12:10:22.995281Z", - "iopub.status.idle": "2024-04-12T12:10:22.999317Z", - "shell.execute_reply": "2024-04-12T12:10:22.998769Z" + "iopub.execute_input": "2024-11-24T09:27:31.858429Z", + "iopub.status.busy": "2024-11-24T09:27:31.858216Z", + "iopub.status.idle": "2024-11-24T09:27:31.862461Z", + "shell.execute_reply": "2024-11-24T09:27:31.861795Z" }, "title": "From https://scikit-learn.org/stable/auto_examples/model_selection/plot_randomized_search.html#sphx-glr-auto-examples-model-selection-plot-randomized-search-py" }, @@ -336,7 +343,7 @@ }, { "cell_type": "markdown", - "id": "ad49376f", + "id": "469691f4", "metadata": {}, "source": [ "We will do 25 tries of random parameter sets, and see what comes out as the best one. If you are using the small example dataset, this should take some second, but may take some minutes with the full set." @@ -345,13 +352,13 @@ { "cell_type": "code", "execution_count": 11, - "id": "bc66efa3", + "id": "79a70a0f", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:23.001598Z", - "iopub.status.busy": "2024-04-12T12:10:23.001400Z", - "iopub.status.idle": "2024-04-12T12:10:27.149245Z", - "shell.execute_reply": "2024-04-12T12:10:27.148640Z" + "iopub.execute_input": "2024-11-24T09:27:31.864936Z", + "iopub.status.busy": "2024-11-24T09:27:31.864708Z", + "iopub.status.idle": "2024-11-24T09:27:36.221386Z", + "shell.execute_reply": "2024-11-24T09:27:36.220369Z" } }, "outputs": [ @@ -359,7 +366,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Runtime: 4.14 for 25 iterations)\n" + "Runtime: 4.35 for 25 iterations)\n" ] } ], @@ -372,19 +379,19 @@ "random_search.fit(mol_list_std_train, y_train.values)\n", "t1 = time()\n", "\n", - "print(f'Runtime: {t1-t0:0.2F} for {n_iter_search} iterations)')" + "print(f\"Runtime: {t1-t0:0.2F} for {n_iter_search} iterations)\")" ] }, { "cell_type": "code", "execution_count": 12, - "id": "b2b3d623", + "id": "b6160cb3", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:27.153307Z", - "iopub.status.busy": "2024-04-12T12:10:27.152284Z", - "iopub.status.idle": "2024-04-12T12:10:27.157929Z", - "shell.execute_reply": "2024-04-12T12:10:27.157394Z" + "iopub.execute_input": "2024-11-24T09:27:36.224876Z", + "iopub.status.busy": "2024-11-24T09:27:36.224667Z", + "iopub.status.idle": "2024-11-24T09:27:36.232647Z", + "shell.execute_reply": "2024-11-24T09:27:36.231571Z" }, "lines_to_next_cell": 0 }, @@ -394,16 +401,16 @@ "output_type": "stream", "text": [ "Model with rank: 1\n", - "Mean validation score: 0.539 (std: 0.090)\n", - "Parameters: {'morganfingerprinttransformer__nBits': 2048, 'morganfingerprinttransformer__radius': 1, 'morganfingerprinttransformer__useCounts': False, 'morganfingerprinttransformer__useFeatures': False, 'ridge__alpha': 10.016632822744322}\n", + "Mean validation score: 0.563 (std: 0.115)\n", + "Parameters: {'morganfingerprinttransformer__fpSize': 1024, 'morganfingerprinttransformer__radius': 2, 'morganfingerprinttransformer__useCounts': False, 'morganfingerprinttransformer__useFeatures': False, 'ridge__alpha': 6.855244257973563}\n", "\n", "Model with rank: 2\n", - "Mean validation score: 0.534 (std: 0.145)\n", - "Parameters: {'morganfingerprinttransformer__nBits': 2048, 'morganfingerprinttransformer__radius': 3, 'morganfingerprinttransformer__useCounts': False, 'morganfingerprinttransformer__useFeatures': False, 'ridge__alpha': 0.7814453905088184}\n", + "Mean validation score: 0.527 (std: 0.086)\n", + "Parameters: {'morganfingerprinttransformer__fpSize': 512, 'morganfingerprinttransformer__radius': 2, 'morganfingerprinttransformer__useCounts': False, 'morganfingerprinttransformer__useFeatures': False, 'ridge__alpha': 13.611425709525077}\n", "\n", "Model with rank: 3\n", - "Mean validation score: 0.526 (std: 0.090)\n", - "Parameters: {'morganfingerprinttransformer__nBits': 4096, 'morganfingerprinttransformer__radius': 1, 'morganfingerprinttransformer__useCounts': False, 'morganfingerprinttransformer__useFeatures': False, 'ridge__alpha': 11.295262712617353}\n", + "Mean validation score: 0.466 (std: 0.149)\n", + "Parameters: {'morganfingerprinttransformer__fpSize': 2048, 'morganfingerprinttransformer__radius': 4, 'morganfingerprinttransformer__useCounts': False, 'morganfingerprinttransformer__useFeatures': True, 'ridge__alpha': 1.383163758398022}\n", "\n" ] } @@ -414,7 +421,7 @@ }, { "cell_type": "markdown", - "id": "219e3e32", + "id": "9a2ea219", "metadata": {}, "source": [ "It can be interesting to see what combinations of hyperparameters gave good results for the cross-validation. Usually the number of bits are in the high end and radius is 2 to 4. But this can vary a bit, as we do a small number of tries for this demo. More extended search with more iterations could maybe find even better and more consistent. solutions" @@ -422,7 +429,7 @@ }, { "cell_type": "markdown", - "id": "630772af", + "id": "6cf91582", "metadata": {}, "source": [ "Let's see if standardization had any influence on this dataset. We build an inference pipeline that includes the standardization object and the best estimator, and run the best estimator directly on the list of test molecules" @@ -431,13 +438,13 @@ { "cell_type": "code", "execution_count": 13, - "id": "cb369a0e", + "id": "4daaf106", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:27.161728Z", - "iopub.status.busy": "2024-04-12T12:10:27.160746Z", - "iopub.status.idle": "2024-04-12T12:10:27.316434Z", - "shell.execute_reply": "2024-04-12T12:10:27.315830Z" + "iopub.execute_input": "2024-11-24T09:27:36.236805Z", + "iopub.status.busy": "2024-11-24T09:27:36.235794Z", + "iopub.status.idle": "2024-11-24T09:27:36.394539Z", + "shell.execute_reply": "2024-11-24T09:27:36.393590Z" } }, "outputs": [ @@ -445,27 +452,29 @@ "name": "stdout", "output_type": "stream", "text": [ - "No Standardization 0.5379\n", - "With Standardization 0.5379\n" + "No Standardization 0.6389\n", + "With Standardization 0.6389\n" ] } ], "source": [ "inference_pipe = make_pipeline(standardizer, random_search.best_estimator_)\n", "\n", - "print(f'No Standardization {random_search.best_estimator_.score(mol_list_test, y_test):0.4F}')\n", - "print(f'With Standardization {inference_pipe.score(mol_list_test, y_test):0.4F}')" + "print(\n", + " f\"No Standardization {random_search.best_estimator_.score(mol_list_test, y_test):0.4F}\"\n", + ")\n", + "print(f\"With Standardization {inference_pipe.score(mol_list_test, y_test):0.4F}\")" ] }, { "cell_type": "markdown", - "id": "e00bae88", + "id": "2d31c059", "metadata": { "lines_to_next_cell": 0, "title": "Building an inference pipeline, it appears our test-data was pretty standard" }, "source": [ - "We see that the dataset already appeared to be in forms that are similar to the ones coming from the standardization. \n", + "We see that the dataset already appeared to be in forms that are similar to the ones coming from the standardization.\n", "\n", "Interestingly the test-set performance often seem to be better than the CV performance during the hyperparameter search. This may be due to the model being refit at the end of the search to the whole training dataset, as the refit parameter on the randomized_search object by default is true. The final model is thus fitted on more data than the individual models during training.\n", "\n", @@ -475,13 +484,13 @@ { "cell_type": "code", "execution_count": 14, - "id": "f6426b23", + "id": "92105568", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:27.320951Z", - "iopub.status.busy": "2024-04-12T12:10:27.319924Z", - "iopub.status.idle": "2024-04-12T12:10:27.337918Z", - "shell.execute_reply": "2024-04-12T12:10:27.337344Z" + "iopub.execute_input": "2024-11-24T09:27:36.397490Z", + "iopub.status.busy": "2024-11-24T09:27:36.397082Z", + "iopub.status.idle": "2024-11-24T09:27:36.411965Z", + "shell.execute_reply": "2024-11-24T09:27:36.411400Z" } }, "outputs": [ @@ -489,23 +498,31 @@ "name": "stdout", "output_type": "stream", "text": [ - "Predictions with no standardization: [5.95334818 5.99768591 5.99768591 6.31509408 6.09785566]\n", - "Predictions with standardization: [5.95334818 5.95334818 5.95334818 5.95334818 5.95334818]\n" + "Predictions with no standardization: [5.89126045 5.97721234 5.97721234 6.03427056 6.03951076]\n", + "Predictions with standardization: [5.89126045 5.89126045 5.89126045 5.89126045 5.89126045]\n" ] } ], "source": [ "# Intergrating the Standardizer and challenge it with some different forms and salts of benzoic acid\n", - "smiles_list = ['c1ccccc1C(=O)[OH]', 'c1ccccc1C(=O)[O-]', 'c1ccccc1C(=O)[O-].[Na+]', 'c1ccccc1C(=O)[O][Na]', 'c1ccccc1C(=O)[O-].C[N+](C)C']\n", + "smiles_list = [\n", + " \"c1ccccc1C(=O)[OH]\",\n", + " \"c1ccccc1C(=O)[O-]\",\n", + " \"c1ccccc1C(=O)[O-].[Na+]\",\n", + " \"c1ccccc1C(=O)[O][Na]\",\n", + " \"c1ccccc1C(=O)[O-].C[N+](C)C\",\n", + "]\n", "mols_list = [Chem.MolFromSmiles(smiles) for smiles in smiles_list]\n", "\n", - "print(f'Predictions with no standardization: {random_search.best_estimator_.predict(mols_list)}')\n", - "print(f'Predictions with standardization: {inference_pipe.predict(mols_list)}')" + "print(\n", + " f\"Predictions with no standardization: {random_search.best_estimator_.predict(mols_list)}\"\n", + ")\n", + "print(f\"Predictions with standardization: {inference_pipe.predict(mols_list)}\")" ] }, { "cell_type": "markdown", - "id": "47345289", + "id": "9d196197", "metadata": {}, "source": [ "Without standardization we get variation in the predictions, but with the standardization object in place, we get the same results. If you want a model that gives different predictions for the different forms, either the standardization need to be removed or the settings changed.\n", @@ -515,7 +532,7 @@ }, { "cell_type": "markdown", - "id": "8e0a8f49", + "id": "824ebc99", "metadata": {}, "source": [] } diff --git a/notebooks/06_hyperparameter_tuning.py b/notebooks/06_hyperparameter_tuning.py index 0747bd2..ba59814 100644 --- a/notebooks/06_hyperparameter_tuning.py +++ b/notebooks/06_hyperparameter_tuning.py @@ -44,10 +44,11 @@ csv_file = "SLC6A4_active_excape_export.csv" if not os.path.exists(csv_file): import urllib.request + url = "https://ndownloader.figshare.com/files/25747817" urllib.request.urlretrieve(url, csv_file) else: - csv_file = '../tests/data/SLC6A4_active_excapedb_subset.csv' + csv_file = "../tests/data/SLC6A4_active_excapedb_subset.csv" # %% [markdown] # The CSV data is loaded into a Pandas dataframe and the PandasTools utility from RDKit is used to add a column with RDKit molecules @@ -64,14 +65,16 @@ # %% -mol_list_train, mol_list_test, y_train, y_test = train_test_split(data.ROMol, data.pXC50, random_state=42) +mol_list_train, mol_list_test, y_train, y_test = train_test_split( + data.ROMol, data.pXC50, random_state=42 +) # %% [markdown] # We will standardize the molecules before modelling. This is best done before the hyperparameter optimizatiion of the featurization with the scikit-mol transformer and regression modelling, as the standardization is otherwise done for every loop in the hyperparameter optimization, which will make it take longer time. # %% -# Probably the recommended way would be to prestandardize the data if there's no changes to the transformer, +# Probably the recommended way would be to prestandardize the data if there's no changes to the transformer, # and then add the standardizer in the inference pipeline. from scikit_mol.standardizer import Standardizer @@ -90,13 +93,13 @@ optimization_pipe = make_pipeline(moltransformer, regressor) - # %% [markdown] # For hyperparameter optimization we import the RandomizedSearchCV class from Scikit-Learn. It will try different random combinations of settings and use internal cross-validation to find the best model. In the end, it will fit the best found parameters on the full set. We also import loguniform, to get a better sampling of some of the parameters. # %% Now hyperparameter tuning from sklearn.model_selection import RandomizedSearchCV -#from sklearn.utils.fixes import loguniform + +# from sklearn.utils.fixes import loguniform from scipy.stats import loguniform # %% [markdown] @@ -111,15 +114,18 @@ # %% -param_dist = {'ridge__alpha': loguniform(1e-2, 1e3), - "morganfingerprinttransformer__nBits": [256,512,1024,2048,4096], - 'morganfingerprinttransformer__radius':[1,2,3,4], - 'morganfingerprinttransformer__useCounts': [True,False], - 'morganfingerprinttransformer__useFeatures':[True,False]} +param_dist = { + "ridge__alpha": loguniform(1e-2, 1e3), + "morganfingerprinttransformer__fpSize": [256, 512, 1024, 2048, 4096], + "morganfingerprinttransformer__radius": [1, 2, 3, 4], + "morganfingerprinttransformer__useCounts": [True, False], + "morganfingerprinttransformer__useFeatures": [True, False], +} # %% [markdown] # The report function was taken from [this example](https://scikit-learn.org/stable/auto_examples/model_selection/plot_randomized_search.html#sphx-glr-auto-examples-model-selection-plot-randomized-search-py) from the scikit learn documentation. + # %% From https://scikit-learn.org/stable/auto_examples/model_selection/plot_randomized_search.html#sphx-glr-auto-examples-model-selection-plot-randomized-search-py # Utility function to report best scores def report(results, n_top=3): @@ -149,7 +155,7 @@ def report(results, n_top=3): random_search.fit(mol_list_std_train, y_train.values) t1 = time() -print(f'Runtime: {t1-t0:0.2F} for {n_iter_search} iterations)') +print(f"Runtime: {t1-t0:0.2F} for {n_iter_search} iterations)") # %% report(random_search.cv_results_) @@ -162,22 +168,32 @@ def report(results, n_top=3): # %% inference_pipe = make_pipeline(standardizer, random_search.best_estimator_) -print(f'No Standardization {random_search.best_estimator_.score(mol_list_test, y_test):0.4F}') -print(f'With Standardization {inference_pipe.score(mol_list_test, y_test):0.4F}') +print( + f"No Standardization {random_search.best_estimator_.score(mol_list_test, y_test):0.4F}" +) +print(f"With Standardization {inference_pipe.score(mol_list_test, y_test):0.4F}") # %% Building an inference pipeline, it appears our test-data was pretty standard [markdown] -# We see that the dataset already appeared to be in forms that are similar to the ones coming from the standardization. +# We see that the dataset already appeared to be in forms that are similar to the ones coming from the standardization. # # Interestingly the test-set performance often seem to be better than the CV performance during the hyperparameter search. This may be due to the model being refit at the end of the search to the whole training dataset, as the refit parameter on the randomized_search object by default is true. The final model is thus fitted on more data than the individual models during training. # # To demonstrate the effect of standartization we can see the difference if we challenge the predictor with different forms of benzoic acid and benzoates. # %% # Intergrating the Standardizer and challenge it with some different forms and salts of benzoic acid -smiles_list = ['c1ccccc1C(=O)[OH]', 'c1ccccc1C(=O)[O-]', 'c1ccccc1C(=O)[O-].[Na+]', 'c1ccccc1C(=O)[O][Na]', 'c1ccccc1C(=O)[O-].C[N+](C)C'] +smiles_list = [ + "c1ccccc1C(=O)[OH]", + "c1ccccc1C(=O)[O-]", + "c1ccccc1C(=O)[O-].[Na+]", + "c1ccccc1C(=O)[O][Na]", + "c1ccccc1C(=O)[O-].C[N+](C)C", +] mols_list = [Chem.MolFromSmiles(smiles) for smiles in smiles_list] -print(f'Predictions with no standardization: {random_search.best_estimator_.predict(mols_list)}') -print(f'Predictions with standardization: {inference_pipe.predict(mols_list)}') +print( + f"Predictions with no standardization: {random_search.best_estimator_.predict(mols_list)}" +) +print(f"Predictions with standardization: {inference_pipe.predict(mols_list)}") # %% [markdown] # Without standardization we get variation in the predictions, but with the standardization object in place, we get the same results. If you want a model that gives different predictions for the different forms, either the standardization need to be removed or the settings changed. diff --git a/notebooks/07_parallel_transforms.ipynb b/notebooks/07_parallel_transforms.ipynb index 36c4ac0..9c111f8 100644 --- a/notebooks/07_parallel_transforms.ipynb +++ b/notebooks/07_parallel_transforms.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "3c3e2734", + "id": "87ed8373", "metadata": {}, "source": [ "# Parallel calculations of transforms\n", @@ -15,13 +15,13 @@ { "cell_type": "code", "execution_count": 1, - "id": "d34a6f7e", + "id": "dac6956a", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:28.855965Z", - "iopub.status.busy": "2024-04-12T12:10:28.855774Z", - "iopub.status.idle": "2024-04-12T12:10:29.593405Z", - "shell.execute_reply": "2024-04-12T12:10:29.592709Z" + "iopub.execute_input": "2024-11-24T09:27:38.302600Z", + "iopub.status.busy": "2024-11-24T09:27:38.302116Z", + "iopub.status.idle": "2024-11-24T09:27:39.171522Z", + "shell.execute_reply": "2024-11-24T09:27:39.170882Z" } }, "outputs": [], @@ -38,7 +38,7 @@ }, { "cell_type": "markdown", - "id": "f73bfd41", + "id": "7c2a81f2", "metadata": {}, "source": [ "## Obtaining the Data\n", @@ -51,13 +51,13 @@ { "cell_type": "code", "execution_count": 2, - "id": "f59b0883", + "id": "f64c418f", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:29.596321Z", - "iopub.status.busy": "2024-04-12T12:10:29.596022Z", - "iopub.status.idle": "2024-04-12T12:10:29.600548Z", - "shell.execute_reply": "2024-04-12T12:10:29.599990Z" + "iopub.execute_input": "2024-11-24T09:27:39.174368Z", + "iopub.status.busy": "2024-11-24T09:27:39.174075Z", + "iopub.status.idle": "2024-11-24T09:27:39.177863Z", + "shell.execute_reply": "2024-11-24T09:27:39.177305Z" } }, "outputs": [], @@ -77,13 +77,13 @@ { "cell_type": "code", "execution_count": 3, - "id": "9cb3cb5c", + "id": "0eabd800", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:29.602922Z", - "iopub.status.busy": "2024-04-12T12:10:29.602713Z", - "iopub.status.idle": "2024-04-12T12:10:29.643748Z", - "shell.execute_reply": "2024-04-12T12:10:29.643141Z" + "iopub.execute_input": "2024-11-24T09:27:39.180191Z", + "iopub.status.busy": "2024-11-24T09:27:39.179937Z", + "iopub.status.idle": "2024-11-24T09:27:39.221096Z", + "shell.execute_reply": "2024-11-24T09:27:39.220386Z" } }, "outputs": [ @@ -105,7 +105,7 @@ }, { "cell_type": "markdown", - "id": "cbdda331", + "id": "4144946e", "metadata": {}, "source": [ "## Evaluating the Impact of Parallelism on Transformations\n", @@ -116,13 +116,13 @@ { "cell_type": "code", "execution_count": 4, - "id": "45c042f0", + "id": "a7f66af7", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:29.646541Z", - "iopub.status.busy": "2024-04-12T12:10:29.646035Z", - "iopub.status.idle": "2024-04-12T12:10:29.651144Z", - "shell.execute_reply": "2024-04-12T12:10:29.650588Z" + "iopub.execute_input": "2024-11-24T09:27:39.223702Z", + "iopub.status.busy": "2024-11-24T09:27:39.223459Z", + "iopub.status.idle": "2024-11-24T09:27:39.228461Z", + "shell.execute_reply": "2024-11-24T09:27:39.227977Z" }, "title": "A demonstration of the speedup that can be had for the descriptor transformer" }, @@ -134,21 +134,687 @@ { "cell_type": "code", "execution_count": 5, - "id": "8f158499", + "id": "a03bc824", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:29.653455Z", - "iopub.status.busy": "2024-04-12T12:10:29.653236Z", - "iopub.status.idle": "2024-04-12T12:10:31.579596Z", - "shell.execute_reply": "2024-04-12T12:10:31.578985Z" + "iopub.execute_input": "2024-11-24T09:27:39.230911Z", + "iopub.status.busy": "2024-11-24T09:27:39.230692Z", + "iopub.status.idle": "2024-11-24T09:27:41.368180Z", + "shell.execute_reply": "2024-11-24T09:27:41.367438Z" } }, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:40] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, { "name": "stdout", "output_type": "stream", "text": [ - "Calculation time on dataset of size 200 with parallel=False:\t1.92 seconds\n" + "Calculation time on dataset of size 200 with parallel=False:\t2.13 seconds\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" ] } ], @@ -163,7 +829,7 @@ }, { "cell_type": "markdown", - "id": "08b2cb6e", + "id": "d304d675", "metadata": {}, "source": [ "\n", @@ -173,13 +839,13 @@ { "cell_type": "code", "execution_count": 6, - "id": "f1b48596", + "id": "c80388e6", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:31.582218Z", - "iopub.status.busy": "2024-04-12T12:10:31.581970Z", - "iopub.status.idle": "2024-04-12T12:10:32.110173Z", - "shell.execute_reply": "2024-04-12T12:10:32.109459Z" + "iopub.execute_input": "2024-11-24T09:27:41.370886Z", + "iopub.status.busy": "2024-11-24T09:27:41.370638Z", + "iopub.status.idle": "2024-11-24T09:27:42.384085Z", + "shell.execute_reply": "2024-11-24T09:27:42.383188Z" } }, "outputs": [ @@ -187,15 +853,4215 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/esben/python_envs/vscode/lib/python3.10/site-packages/numpy/core/fromnumeric.py:59: FutureWarning: 'Series.swapaxes' is deprecated and will be removed in a future version. Please use 'Series.transpose' instead.\n", - " return bound(*args, **kwds)\n" + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/esben/python_envs/vscode/lib/python3.10/site-packages/numpy/core/fromnumeric.py:59: FutureWarning: 'Series.swapaxes' is deprecated and will be removed in a future version. Please use 'Series.transpose' instead.\n", + " return bound(*args, **kwds)\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:41] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:27:42] DEPRECATION WARNING: please use MorganGenerator\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ - "Calculation time on dataset of size 200 with parallel=True:\t0.52 seconds\n" + "Calculation time on dataset of size 200 with parallel=True:\t1.01 seconds\n" ] } ], @@ -207,7 +5073,7 @@ }, { "cell_type": "markdown", - "id": "267de7b7", + "id": "731bd13a", "metadata": {}, "source": [ "We've seen that parallelism can help speed up our transformations, with the degree of speedup depending on the number of CPU cores available. However, it's worth noting that there may be some overhead associated with the process of splitting the dataset, pickling objects and functions, and passing them to the parallel child processes. As a result, it may not always be worthwhile to use parallelism, particularly for smaller datasets or certain types of fingerprints.\n", @@ -220,13 +5086,13 @@ { "cell_type": "code", "execution_count": 7, - "id": "f8b61e6c", + "id": "ef6d2b0c", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:32.113041Z", - "iopub.status.busy": "2024-04-12T12:10:32.112800Z", - "iopub.status.idle": "2024-04-12T12:10:32.220711Z", - "shell.execute_reply": "2024-04-12T12:10:32.219973Z" + "iopub.execute_input": "2024-11-24T09:27:42.387160Z", + "iopub.status.busy": "2024-11-24T09:27:42.386886Z", + "iopub.status.idle": "2024-11-24T09:27:42.484867Z", + "shell.execute_reply": "2024-11-24T09:27:42.484222Z" }, "lines_to_next_cell": 2, "title": "Some of the benchmarking plots" @@ -256,7 +5122,7 @@ }, { "cell_type": "markdown", - "id": "9c29a58f", + "id": "2aac85b1", "metadata": {}, "source": [ "Interestingly, we observed that parallelism actually took longer to calculate the fingerprints in some cases, which is a perfect illustration of the overhead issue associated with parallelism. Generally, the faster the fingerprint calculation in itself, the larger the dataset needs to be for parallelism to be worthwhile. For example, the Descriptor transformer, which is one of the slowest, can benefit even for smaller datasets, while for faster fingerprint types like Morgan, Atompairs, and Topological Torsion fingerprints, the dataset needs to be larger.\n", diff --git a/notebooks/08_external_library_skopt.ipynb b/notebooks/08_external_library_skopt.ipynb index de50fee..8d8121f 100644 --- a/notebooks/08_external_library_skopt.ipynb +++ b/notebooks/08_external_library_skopt.ipynb @@ -3,8 +3,14 @@ { "cell_type": "code", "execution_count": 1, - "id": "7111cd27", + "id": "c0f4155f", "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:27:44.212745Z", + "iopub.status.busy": "2024-11-24T09:27:44.212299Z", + "iopub.status.idle": "2024-11-24T09:27:47.165987Z", + "shell.execute_reply": "2024-11-24T09:27:47.162995Z" + }, "title": "Needs scikit-optimize" }, "outputs": [ @@ -12,15 +18,21 @@ "name": "stdout", "output_type": "stream", "text": [ - "Requirement already satisfied: scikit-optimize in /home/esben/python_envs/vscode/lib/python3.10/site-packages (0.10.1)\n", - "Requirement already satisfied: packaging>=21.3 in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from scikit-optimize) (23.2)\n", - "Requirement already satisfied: pyaml>=16.9 in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from scikit-optimize) (23.12.0)\n", - "Requirement already satisfied: joblib>=0.11 in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from scikit-optimize) (1.3.2)\n", - "Requirement already satisfied: scikit-learn>=1.0.0 in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from scikit-optimize) (1.3.1)\n", - "Requirement already satisfied: scipy>=1.1.0 in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from scikit-optimize) (1.11.3)\n", - "Requirement already satisfied: numpy>=1.20.3 in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from scikit-optimize) (1.26.0)\n", - "Requirement already satisfied: PyYAML in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from pyaml>=16.9->scikit-optimize) (6.0.1)\n", - "Requirement already satisfied: threadpoolctl>=2.0.0 in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from scikit-learn>=1.0.0->scikit-optimize) (3.2.0)\n" + "Requirement already satisfied: scikit-optimize in /home/esben/python_envs/vscode/lib/python3.10/site-packages (0.10.2)\r\n", + "Requirement already satisfied: scikit-learn>=1.0.0 in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from scikit-optimize) (1.5.2)\r\n", + "Requirement already satisfied: joblib>=0.11 in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from scikit-optimize) (1.3.2)\r\n", + "Requirement already satisfied: scipy>=1.1.0 in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from scikit-optimize) (1.11.3)\r\n", + "Requirement already satisfied: packaging>=21.3 in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from scikit-optimize) (23.2)\r\n", + "Requirement already satisfied: pyaml>=16.9 in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from scikit-optimize) (23.12.0)\r\n", + "Requirement already satisfied: numpy>=1.20.3 in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from scikit-optimize) (1.26.4)\r\n", + "Requirement already satisfied: PyYAML in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from pyaml>=16.9->scikit-optimize) (6.0.1)\r\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: threadpoolctl>=3.1.0 in /home/esben/python_envs/vscode/lib/python3.10/site-packages (from scikit-learn>=1.0.0->scikit-optimize) (3.2.0)\r\n" ] } ], @@ -31,9 +43,14 @@ { "cell_type": "code", "execution_count": 2, - "id": "5648edb8", + "id": "49f80040", "metadata": { - "lines_to_next_cell": 0 + "execution": { + "iopub.execute_input": "2024-11-24T09:27:47.174431Z", + "iopub.status.busy": "2024-11-24T09:27:47.173616Z", + "iopub.status.idle": "2024-11-24T09:27:47.507299Z", + "shell.execute_reply": "2024-11-24T09:27:47.506634Z" + } }, "outputs": [], "source": [ @@ -45,8 +62,15 @@ { "cell_type": "code", "execution_count": 3, - "id": "6825d2cd", - "metadata": {}, + "id": "f1268213", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:27:47.510217Z", + "iopub.status.busy": "2024-11-24T09:27:47.509924Z", + "iopub.status.idle": "2024-11-24T09:27:48.065273Z", + "shell.execute_reply": "2024-11-24T09:27:48.064596Z" + } + }, "outputs": [], "source": [ "from sklearn.linear_model import Ridge\n", @@ -61,9 +85,14 @@ { "cell_type": "code", "execution_count": 4, - "id": "12f97eb7", + "id": "7239cf27", "metadata": { - "lines_to_next_cell": 0 + "execution": { + "iopub.execute_input": "2024-11-24T09:27:48.068114Z", + "iopub.status.busy": "2024-11-24T09:27:48.067809Z", + "iopub.status.idle": "2024-11-24T09:27:48.129879Z", + "shell.execute_reply": "2024-11-24T09:27:48.129159Z" + } }, "outputs": [], "source": [ @@ -76,8 +105,15 @@ { "cell_type": "code", "execution_count": 5, - "id": "3e9f36e9", - "metadata": {}, + "id": "aabbba9d", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:27:48.132744Z", + "iopub.status.busy": "2024-11-24T09:27:48.132424Z", + "iopub.status.idle": "2024-11-24T09:27:48.173318Z", + "shell.execute_reply": "2024-11-24T09:27:48.172690Z" + } + }, "outputs": [], "source": [ "full_set = False\n", @@ -86,32 +122,443 @@ " csv_file = \"SLC6A4_active_excape_export.csv\"\n", " if not os.path.exists(csv_file):\n", " import urllib.request\n", + "\n", " url = \"https://ndownloader.figshare.com/files/25747817\"\n", " urllib.request.urlretrieve(url, csv_file)\n", "else:\n", - " csv_file = '../tests/data/SLC6A4_active_excapedb_subset.csv'\n", + " csv_file = \"../tests/data/SLC6A4_active_excapedb_subset.csv\"\n", "\n", "data = pd.read_csv(csv_file)\n", "trf = SmilesToMolTransformer()\n", - "data['ROMol'] = trf.transform(data.SMILES.values).flatten()" + "data[\"ROMol\"] = trf.transform(data.SMILES.values).flatten()" ] }, { "cell_type": "code", "execution_count": 6, - "id": "67d54492", + "id": "488a3e82", "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:27:48.176242Z", + "iopub.status.busy": "2024-11-24T09:27:48.175854Z", + "iopub.status.idle": "2024-11-24T09:27:48.188154Z", + "shell.execute_reply": "2024-11-24T09:27:48.187463Z" + }, "lines_to_next_cell": 0 }, "outputs": [ { "data": { "text/html": [ - "
Pipeline(steps=[('morganfingerprinttransformer',\n",
+       "
Pipeline(steps=[('morganfingerprinttransformer',\n",
        "                 MorganFingerprintTransformer()),\n",
-       "                ('ridge', Ridge())])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
MorganFingerprintTransformer()
" ], "text/plain": [ "Pipeline(steps=[('morganfingerprinttransformer',\n", @@ -132,14 +579,21 @@ { "cell_type": "code", "execution_count": 7, - "id": "e3ce7fc0", - "metadata": {}, + "id": "44811b8e", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:27:48.190932Z", + "iopub.status.busy": "2024-11-24T09:27:48.190654Z", + "iopub.status.idle": "2024-11-24T09:27:48.195099Z", + "shell.execute_reply": "2024-11-24T09:27:48.194411Z" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{'memory': None, 'steps': [('morganfingerprinttransformer', MorganFingerprintTransformer()), ('ridge', Ridge())], 'verbose': False, 'morganfingerprinttransformer': MorganFingerprintTransformer(), 'ridge': Ridge(), 'morganfingerprinttransformer__nBits': 2048, 'morganfingerprinttransformer__parallel': False, 'morganfingerprinttransformer__radius': 2, 'morganfingerprinttransformer__useBondTypes': True, 'morganfingerprinttransformer__useChirality': False, 'morganfingerprinttransformer__useCounts': False, 'morganfingerprinttransformer__useFeatures': False, 'ridge__alpha': 1.0, 'ridge__copy_X': True, 'ridge__fit_intercept': True, 'ridge__max_iter': None, 'ridge__positive': False, 'ridge__random_state': None, 'ridge__solver': 'auto', 'ridge__tol': 0.0001}\n" + "{'memory': None, 'steps': [('morganfingerprinttransformer', MorganFingerprintTransformer()), ('ridge', Ridge())], 'verbose': False, 'morganfingerprinttransformer': MorganFingerprintTransformer(), 'ridge': Ridge(), 'morganfingerprinttransformer__fpSize': 2048, 'morganfingerprinttransformer__parallel': False, 'morganfingerprinttransformer__radius': 2, 'morganfingerprinttransformer__safe_inference_mode': False, 'morganfingerprinttransformer__useBondTypes': True, 'morganfingerprinttransformer__useChirality': False, 'morganfingerprinttransformer__useCounts': False, 'morganfingerprinttransformer__useFeatures': False, 'ridge__alpha': 1.0, 'ridge__copy_X': True, 'ridge__fit_intercept': True, 'ridge__max_iter': None, 'ridge__positive': False, 'ridge__random_state': None, 'ridge__solver': 'auto', 'ridge__tol': 0.0001}\n" ] } ], @@ -150,23 +604,28 @@ { "cell_type": "code", "execution_count": 8, - "id": "0a155dd3", + "id": "49eb7dbe", "metadata": { - "lines_to_next_cell": 0 + "execution": { + "iopub.execute_input": "2024-11-24T09:27:48.197795Z", + "iopub.status.busy": "2024-11-24T09:27:48.197479Z", + "iopub.status.idle": "2024-11-24T09:27:48.205564Z", + "shell.execute_reply": "2024-11-24T09:27:48.205022Z" + } }, "outputs": [], "source": [ "max_bits = 4096\n", "\n", "morgan_space = [\n", - " Categorical([True, False], name='morganfingerprinttransformer__useCounts'),\n", - " Categorical([True, False], name='morganfingerprinttransformer__useFeatures'),\n", - " Integer(512,max_bits, name='morganfingerprinttransformer__nBits'),\n", - " Integer(1,3, name='morganfingerprinttransformer__radius')\n", + " Categorical([True, False], name=\"morganfingerprinttransformer__useCounts\"),\n", + " Categorical([True, False], name=\"morganfingerprinttransformer__useFeatures\"),\n", + " Integer(512, max_bits, name=\"morganfingerprinttransformer__fpSize\"),\n", + " Integer(1, 3, name=\"morganfingerprinttransformer__radius\"),\n", "]\n", "\n", "\n", - "regressor_space = [Real(1e-2, 1e3, \"log-uniform\", name='ridge__alpha')]\n", + "regressor_space = [Real(1e-2, 1e3, \"log-uniform\", name=\"ridge__alpha\")]\n", "\n", "search_space = morgan_space + regressor_space" ] @@ -174,9 +633,14 @@ { "cell_type": "code", "execution_count": 9, - "id": "abb750a3", + "id": "3818beb2", "metadata": { - "lines_to_next_cell": 0 + "execution": { + "iopub.execute_input": "2024-11-24T09:27:48.207944Z", + "iopub.status.busy": "2024-11-24T09:27:48.207727Z", + "iopub.status.idle": "2024-11-24T09:27:48.211453Z", + "shell.execute_reply": "2024-11-24T09:27:48.210957Z" + } }, "outputs": [], "source": [ @@ -186,15 +650,29 @@ " print(f\"{key}:{value} - {type(value)}\")\n", " pipe.set_params(**params)\n", "\n", - " return -np.mean(cross_val_score(pipe, data.ROMol, data.pXC50, cv=2, n_jobs=-1,\n", - " scoring=\"neg_mean_absolute_error\"))" + " return -np.mean(\n", + " cross_val_score(\n", + " pipe,\n", + " data.ROMol,\n", + " data.pXC50,\n", + " cv=2,\n", + " n_jobs=-1,\n", + " scoring=\"neg_mean_absolute_error\",\n", + " )\n", + " )" ] }, { "cell_type": "code", "execution_count": 10, - "id": "8b6a546c", + "id": "aa6b3af8", "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:27:48.213752Z", + "iopub.status.busy": "2024-11-24T09:27:48.213531Z", + "iopub.status.idle": "2024-11-24T09:27:50.948132Z", + "shell.execute_reply": "2024-11-24T09:27:50.947483Z" + }, "lines_to_next_cell": 0, "title": "THIS takes forever on my machine with a GradientBoostingRegressor" }, @@ -205,7 +683,7 @@ "text": [ "morganfingerprinttransformer__useCounts:False - \n", "morganfingerprinttransformer__useFeatures:False - \n", - "morganfingerprinttransformer__nBits:3587 - \n", + "morganfingerprinttransformer__fpSize:3587 - \n", "morganfingerprinttransformer__radius:3 - \n", "ridge__alpha:13.116515715358098 - \n" ] @@ -216,47 +694,65 @@ "text": [ "morganfingerprinttransformer__useCounts:True - \n", "morganfingerprinttransformer__useFeatures:True - \n", - "morganfingerprinttransformer__nBits:715 - \n", + "morganfingerprinttransformer__fpSize:715 - \n", "morganfingerprinttransformer__radius:2 - \n", - "ridge__alpha:2.445263057083992 - \n", + "ridge__alpha:2.445263057083992 - \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "morganfingerprinttransformer__useCounts:False - \n", "morganfingerprinttransformer__useFeatures:True - \n", - "morganfingerprinttransformer__nBits:1920 - \n", + "morganfingerprinttransformer__fpSize:1920 - \n", "morganfingerprinttransformer__radius:3 - \n", "ridge__alpha:0.48638570461894715 - \n", "morganfingerprinttransformer__useCounts:False - \n", "morganfingerprinttransformer__useFeatures:True - \n", - "morganfingerprinttransformer__nBits:3942 - \n", + "morganfingerprinttransformer__fpSize:3942 - \n", "morganfingerprinttransformer__radius:1 - \n", "ridge__alpha:224.09712855921126 - \n", "morganfingerprinttransformer__useCounts:True - \n", "morganfingerprinttransformer__useFeatures:False - \n", - "morganfingerprinttransformer__nBits:2377 - \n", + "morganfingerprinttransformer__fpSize:2377 - \n", "morganfingerprinttransformer__radius:2 - \n", - "ridge__alpha:40.10174523739503 - \n", + "ridge__alpha:40.10174523739503 - \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "morganfingerprinttransformer__useCounts:False - \n", "morganfingerprinttransformer__useFeatures:False - \n", - "morganfingerprinttransformer__nBits:3231 - \n", + "morganfingerprinttransformer__fpSize:3231 - \n", "morganfingerprinttransformer__radius:1 - \n", "ridge__alpha:2.333469328026273 - \n", "morganfingerprinttransformer__useCounts:True - \n", "morganfingerprinttransformer__useFeatures:False - \n", - "morganfingerprinttransformer__nBits:1288 - \n", + "morganfingerprinttransformer__fpSize:1288 - \n", "morganfingerprinttransformer__radius:1 - \n", "ridge__alpha:0.41754668393896904 - \n", "morganfingerprinttransformer__useCounts:True - \n", "morganfingerprinttransformer__useFeatures:True - \n", - "morganfingerprinttransformer__nBits:1897 - \n", + "morganfingerprinttransformer__fpSize:1897 - \n", "morganfingerprinttransformer__radius:3 - \n", "ridge__alpha:1.777255838269662 - \n", "morganfingerprinttransformer__useCounts:False - \n", "morganfingerprinttransformer__useFeatures:False - \n", - "morganfingerprinttransformer__nBits:868 - \n", + "morganfingerprinttransformer__fpSize:868 - \n", "morganfingerprinttransformer__radius:3 - \n", - "ridge__alpha:18.43742127649598 - \n", + "ridge__alpha:18.43742127649598 - \n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "morganfingerprinttransformer__useCounts:True - \n", "morganfingerprinttransformer__useFeatures:True - \n", - "morganfingerprinttransformer__nBits:3202 - \n", + "morganfingerprinttransformer__fpSize:3202 - \n", "morganfingerprinttransformer__radius:2 - \n", "ridge__alpha:0.4219258607446576 - \n" ] @@ -281,8 +777,14 @@ { "cell_type": "code", "execution_count": 11, - "id": "243f5309", + "id": "42d8fc82", "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:27:50.951138Z", + "iopub.status.busy": "2024-11-24T09:27:50.950922Z", + "iopub.status.idle": "2024-11-24T09:27:50.956593Z", + "shell.execute_reply": "2024-11-24T09:27:50.956046Z" + }, "lines_to_next_cell": 0 }, "outputs": [ @@ -291,20 +793,27 @@ "output_type": "stream", "text": [ "Best parameters:\n", - "{'morganfingerprinttransformer__useCounts': False, 'morganfingerprinttransformer__useFeatures': False, 'morganfingerprinttransformer__nBits': 3231, 'morganfingerprinttransformer__radius': 1, 'ridge__alpha': 2.333469328026273}\n" + "{'morganfingerprinttransformer__useCounts': False, 'morganfingerprinttransformer__useFeatures': False, 'morganfingerprinttransformer__fpSize': 3231, 'morganfingerprinttransformer__radius': 1, 'ridge__alpha': 2.333469328026273}\n" ] } ], "source": [ "print(\"\"\"Best parameters:\"\"\")\n", - "print({param.name:value for param,value in zip(pipe_gp.space, pipe_gp.x) })" + "print({param.name: value for param, value in zip(pipe_gp.space, pipe_gp.x)})" ] }, { "cell_type": "code", "execution_count": 12, - "id": "b95b09d1", - "metadata": {}, + "id": "c5e9fda9", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:27:50.959607Z", + "iopub.status.busy": "2024-11-24T09:27:50.959088Z", + "iopub.status.idle": "2024-11-24T09:27:51.588690Z", + "shell.execute_reply": "2024-11-24T09:27:51.587982Z" + } + }, "outputs": [ { "data": { @@ -329,13 +838,14 @@ ], "source": [ "from skopt.plots import plot_convergence\n", + "\n", "plot_convergence(pipe_gp)" ] }, { "cell_type": "code", "execution_count": null, - "id": "8371d672", + "id": "aebdc43d", "metadata": {}, "outputs": [], "source": [] @@ -344,8 +854,7 @@ "metadata": { "jupytext": { "cell_metadata_filter": "title,-all", - "formats": "ipynb,py:percent", - "main_language": "python" + "formats": "ipynb,py:percent" }, "kernelspec": { "display_name": "vscode", diff --git a/notebooks/08_external_library_skopt.py b/notebooks/08_external_library_skopt.py index 4254251..090983e 100644 --- a/notebooks/08_external_library_skopt.py +++ b/notebooks/08_external_library_skopt.py @@ -21,6 +21,7 @@ import os import numpy as np import pandas as pd + # %% from sklearn.linear_model import Ridge from sklearn.model_selection import cross_val_score @@ -35,6 +36,7 @@ from skopt.utils import use_named_args from skopt import gp_minimize + # %% full_set = False @@ -42,14 +44,15 @@ csv_file = "SLC6A4_active_excape_export.csv" if not os.path.exists(csv_file): import urllib.request + url = "https://ndownloader.figshare.com/files/25747817" urllib.request.urlretrieve(url, csv_file) else: - csv_file = '../tests/data/SLC6A4_active_excapedb_subset.csv' + csv_file = "../tests/data/SLC6A4_active_excapedb_subset.csv" data = pd.read_csv(csv_file) trf = SmilesToMolTransformer() -data['ROMol'] = trf.transform(data.SMILES.values).flatten() +data["ROMol"] = trf.transform(data.SMILES.values).flatten() # %% pipe = make_pipeline(MorganFingerprintTransformer(), Ridge()) @@ -61,16 +64,18 @@ max_bits = 4096 morgan_space = [ - Categorical([True, False], name='morganfingerprinttransformer__useCounts'), - Categorical([True, False], name='morganfingerprinttransformer__useFeatures'), - Integer(512,max_bits, name='morganfingerprinttransformer__nBits'), - Integer(1,3, name='morganfingerprinttransformer__radius') + Categorical([True, False], name="morganfingerprinttransformer__useCounts"), + Categorical([True, False], name="morganfingerprinttransformer__useFeatures"), + Integer(512, max_bits, name="morganfingerprinttransformer__fpSize"), + Integer(1, 3, name="morganfingerprinttransformer__radius"), ] -regressor_space = [Real(1e-2, 1e3, "log-uniform", name='ridge__alpha')] +regressor_space = [Real(1e-2, 1e3, "log-uniform", name="ridge__alpha")] search_space = morgan_space + regressor_space + + # %% @use_named_args(search_space) def objective(**params): @@ -78,17 +83,28 @@ def objective(**params): print(f"{key}:{value} - {type(value)}") pipe.set_params(**params) - return -np.mean(cross_val_score(pipe, data.ROMol, data.pXC50, cv=2, n_jobs=-1, - scoring="neg_mean_absolute_error")) + return -np.mean( + cross_val_score( + pipe, + data.ROMol, + data.pXC50, + cv=2, + n_jobs=-1, + scoring="neg_mean_absolute_error", + ) + ) + + # %% THIS takes forever on my machine with a GradientBoostingRegressor pipe_gp = gp_minimize(objective, search_space, n_calls=10, random_state=0) "Best score=%.4f" % pipe_gp.fun # %% print("""Best parameters:""") -print({param.name:value for param,value in zip(pipe_gp.space, pipe_gp.x) }) +print({param.name: value for param, value in zip(pipe_gp.space, pipe_gp.x)}) # %% from skopt.plots import plot_convergence + plot_convergence(pipe_gp) # %% diff --git a/notebooks/09_Combinatorial_Method_Usage_with_FingerPrint_Transformers.ipynb b/notebooks/09_Combinatorial_Method_Usage_with_FingerPrint_Transformers.ipynb index 8e4e2fd..18bc9ec 100644 --- a/notebooks/09_Combinatorial_Method_Usage_with_FingerPrint_Transformers.ipynb +++ b/notebooks/09_Combinatorial_Method_Usage_with_FingerPrint_Transformers.ipynb @@ -2,6 +2,7 @@ "cells": [ { "cell_type": "markdown", + "id": "0e70fca3", "metadata": {}, "source": [ "# Example: Using Multiple Different Fingerprint Transformer\n", @@ -14,7 +15,7 @@ "* Training Phase\n", "* Analysis\n", "\n", - "Authors: @VincentAlexanderScholz, @RiesBen \n", + "Authors: @VincentAlexanderScholz, @RiesBen\n", "\n", "## Imports:\n", "First we will import all the stuff that we will need for our work.\n" @@ -23,12 +24,13 @@ { "cell_type": "code", "execution_count": 1, + "id": "b705b5c9", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:33.739171Z", - "iopub.status.busy": "2024-04-12T12:10:33.738827Z", - "iopub.status.idle": "2024-04-12T12:10:34.837344Z", - "shell.execute_reply": "2024-04-12T12:10:34.836672Z" + "iopub.execute_input": "2024-11-24T09:27:55.508365Z", + "iopub.status.busy": "2024-11-24T09:27:55.507967Z", + "iopub.status.idle": "2024-11-24T09:27:56.807362Z", + "shell.execute_reply": "2024-11-24T09:27:56.806654Z" }, "lines_to_next_cell": 2 }, @@ -52,6 +54,7 @@ }, { "cell_type": "markdown", + "id": "2a4eb825", "metadata": {}, "source": [ "## Get Data:\n", @@ -64,12 +67,13 @@ { "cell_type": "code", "execution_count": 2, + "id": "34b2618a", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:10:34.840322Z", - "iopub.status.busy": "2024-04-12T12:10:34.839978Z", - "iopub.status.idle": "2024-04-12T12:10:34.881015Z", - "shell.execute_reply": "2024-04-12T12:10:34.880411Z" + "iopub.execute_input": "2024-11-24T09:27:56.810246Z", + "iopub.status.busy": "2024-11-24T09:27:56.809941Z", + "iopub.status.idle": "2024-11-24T09:27:56.851202Z", + "shell.execute_reply": "2024-11-24T09:27:56.850568Z" } }, "outputs": [ @@ -89,12 +93,13 @@ " csv_file = \"SLC6A4_active_excape_export.csv\"\n", " if not os.path.exists(csv_file):\n", " import urllib.request\n", + "\n", " url = \"https://ndownloader.figshare.com/files/25747817\"\n", " urllib.request.urlretrieve(url, csv_file)\n", "else:\n", - " csv_file = '../tests/data/SLC6A4_active_excapedb_subset.csv'\n", + " csv_file = \"../tests/data/SLC6A4_active_excapedb_subset.csv\"\n", "\n", - "#Parse Database\n", + "# Parse Database\n", "data = pd.read_csv(csv_file)\n", "\n", "PandasTools.AddMoleculeColumnToFrame(data, smilesCol=\"SMILES\")\n", @@ -103,12 +108,13 @@ }, { "cell_type": "markdown", + "id": "b8dba759", "metadata": {}, "source": [ "## Build Pipeline:\n", "In this stage we will build the Pipeline consisting of the featurization part (finger print transformers) and the model part (Ridge Regression).\n", "\n", - "Note that the featurization in this section is an hyperparameter, living in `param_grid`, and the `\"fp_transformer\"` string is just a placeholder, being replaced during pipeline execution. \n", + "Note that the featurization in this section is an hyperparameter, living in `param_grid`, and the `\"fp_transformer\"` string is just a placeholder, being replaced during pipeline execution.\n", "\n", "This way we can define multiple different scenarios in `param_grid`, that allow us to rapidly explore different combinations of settings and methodologies." ] @@ -116,16 +122,13 @@ { "cell_type": "code", "execution_count": 3, + "id": "e06042cc", "metadata": { - "ExecuteTime": { - "end_time": "2023-09-22T11:29:15.949644Z", - "start_time": "2023-09-22T11:29:15.461010Z" - }, "execution": { - "iopub.execute_input": "2024-04-12T12:10:34.883521Z", - "iopub.status.busy": "2024-04-12T12:10:34.883306Z", - "iopub.status.idle": "2024-04-12T12:10:34.893319Z", - "shell.execute_reply": "2024-04-12T12:10:34.892759Z" + "iopub.execute_input": "2024-11-24T09:27:56.854051Z", + "iopub.status.busy": "2024-11-24T09:27:56.853508Z", + "iopub.status.idle": "2024-11-24T09:27:56.863947Z", + "shell.execute_reply": "2024-11-24T09:27:56.863371Z" } }, "outputs": [ @@ -134,7 +137,7 @@ "text/plain": [ "[{'fp_transformer': [MorganFingerprintTransformer(),\n", " AvalonFingerprintTransformer()],\n", - " 'fp_transformer__nBits': [256, 512, 1024, 2048, 4096],\n", + " 'fp_transformer__fpSize': [256, 512, 1024, 2048, 4096],\n", " 'regressor__alpha': array([0.1 , 0.325, 0.55 , 0.775, 1. ])},\n", " {'fp_transformer': [RDKitFingerprintTransformer(),\n", " AtomPairFingerprintTransformer(),\n", @@ -150,24 +153,35 @@ "source": [ "\n", "regressor = Ridge()\n", - "optimization_pipe = Pipeline([(\"fp_transformer\", \"fp_transformer\"), # this is a placeholder for different transformers\n", - " (\"regressor\", regressor)])\n", - "\n", - "param_grid = [ # Here pass different Options and Approaches\n", + "optimization_pipe = Pipeline(\n", + " [\n", + " (\n", + " \"fp_transformer\",\n", + " \"fp_transformer\",\n", + " ), # this is a placeholder for different transformers\n", + " (\"regressor\", regressor),\n", + " ]\n", + ")\n", + "\n", + "param_grid = [ # Here pass different Options and Approaches\n", " {\n", - " \"fp_transformer\": [fingerprints.MorganFingerprintTransformer(),\n", - " fingerprints.AvalonFingerprintTransformer()],\n", - " \"fp_transformer__nBits\": [2**x for x in range(8,13)],\n", + " \"fp_transformer\": [\n", + " fingerprints.MorganFingerprintTransformer(),\n", + " fingerprints.AvalonFingerprintTransformer(),\n", + " ],\n", + " \"fp_transformer__fpSize\": [2**x for x in range(8, 13)],\n", " },\n", " {\n", - " \"fp_transformer\": [fingerprints.RDKitFingerprintTransformer(),\n", - " fingerprints.AtomPairFingerprintTransformer(),\n", - " fingerprints.MACCSKeysFingerprintTransformer()], \n", + " \"fp_transformer\": [\n", + " fingerprints.RDKitFingerprintTransformer(),\n", + " fingerprints.AtomPairFingerprintTransformer(),\n", + " fingerprints.MACCSKeysFingerprintTransformer(),\n", + " ],\n", " },\n", "]\n", "\n", "global_options = {\n", - " \"regressor__alpha\": np.linspace(0.1,1,5),\n", + " \"regressor__alpha\": np.linspace(0.1, 1, 5),\n", "}\n", "\n", "[params.update(global_options) for params in param_grid]\n", @@ -177,6 +191,7 @@ }, { "cell_type": "markdown", + "id": "521aa24a", "metadata": {}, "source": [ "## Train Model\n", @@ -186,16 +201,13 @@ { "cell_type": "code", "execution_count": 4, + "id": "f1cf66df", "metadata": { - "ExecuteTime": { - "end_time": "2023-09-22T11:29:15.960939Z", - "start_time": "2023-09-22T11:29:15.461078Z" - }, "execution": { - "iopub.execute_input": "2024-04-12T12:10:34.895684Z", - "iopub.status.busy": "2024-04-12T12:10:34.895457Z", - "iopub.status.idle": "2024-04-12T12:11:08.386413Z", - "shell.execute_reply": "2024-04-12T12:11:08.385791Z" + "iopub.execute_input": "2024-11-24T09:27:56.866817Z", + "iopub.status.busy": "2024-11-24T09:27:56.866251Z", + "iopub.status.idle": "2024-11-24T09:28:28.265183Z", + "shell.execute_reply": "2024-11-24T09:28:28.264595Z" } }, "outputs": [ @@ -203,28 +215,30 @@ "name": "stdout", "output_type": "stream", "text": [ - "Runtime: 33.48\n" + "Runtime: 31.39\n" ] } ], "source": [ "# Split Data\n", - "mol_list_train, mol_list_test, y_train, y_test = train_test_split(data.ROMol, data.pXC50, random_state=0)\n", + "mol_list_train, mol_list_test, y_train, y_test = train_test_split(\n", + " data.ROMol, data.pXC50, random_state=0\n", + ")\n", "\n", "# Define Search Process\n", - "grid = GridSearchCV(optimization_pipe, n_jobs=1,\n", - " param_grid=param_grid)\n", + "grid = GridSearchCV(optimization_pipe, n_jobs=1, param_grid=param_grid)\n", "\n", "# Train\n", "t0 = time()\n", "grid.fit(mol_list_train, y_train.values)\n", "t1 = time()\n", "\n", - "print(f'Runtime: {t1-t0:0.2F}')" + "print(f\"Runtime: {t1-t0:0.2F}\")" ] }, { "cell_type": "markdown", + "id": "55aa1549", "metadata": {}, "source": [ "## Analysis\n", @@ -235,12 +249,13 @@ { "cell_type": "code", "execution_count": 5, + "id": "f80006f8", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:08.390298Z", - "iopub.status.busy": "2024-04-12T12:11:08.389259Z", - "iopub.status.idle": "2024-04-12T12:11:08.443953Z", - "shell.execute_reply": "2024-04-12T12:11:08.443334Z" + "iopub.execute_input": "2024-11-24T09:28:28.268630Z", + "iopub.status.busy": "2024-11-24T09:28:28.268131Z", + "iopub.status.idle": "2024-11-24T09:28:28.320208Z", + "shell.execute_reply": "2024-11-24T09:28:28.319598Z" } }, "outputs": [ @@ -270,7 +285,7 @@ " mean_score_time\n", " std_score_time\n", " param_fp_transformer\n", - " param_fp_transformer__nBits\n", + " param_fp_transformer__fpSize\n", " param_regressor__alpha\n", " params\n", " split0_test_score\n", @@ -286,13 +301,13 @@ " \n", " \n", " 0\n", - " 0.025447\n", - " 0.002689\n", - " 0.003600\n", - " 0.000743\n", + " 0.011822\n", + " 0.001013\n", + " 0.003596\n", + " 0.001311\n", " MorganFingerprintTransformer()\n", - " 256\n", - " 0.1\n", + " 256.0\n", + " 0.100\n", " {'fp_transformer': MorganFingerprintTransforme...\n", " 0.017975\n", " 0.394682\n", @@ -305,12 +320,12 @@ " \n", " \n", " 1\n", - " 0.025515\n", - " 0.001279\n", - " 0.003084\n", - " 0.000064\n", + " 0.010119\n", + " 0.000152\n", + " 0.002832\n", + " 0.000070\n", " MorganFingerprintTransformer()\n", - " 256\n", + " 256.0\n", " 0.325\n", " {'fp_transformer': MorganFingerprintTransforme...\n", " 0.078758\n", @@ -324,13 +339,13 @@ " \n", " \n", " 2\n", - " 0.026243\n", - " 0.000330\n", - " 0.003101\n", - " 0.000081\n", + " 0.010302\n", + " 0.000429\n", + " 0.003310\n", + " 0.000967\n", " MorganFingerprintTransformer()\n", - " 256\n", - " 0.55\n", + " 256.0\n", + " 0.550\n", " {'fp_transformer': MorganFingerprintTransforme...\n", " 0.128221\n", " 0.490253\n", @@ -343,12 +358,12 @@ " \n", " \n", " 3\n", - " 0.026571\n", - " 0.001018\n", - " 0.003208\n", - " 0.000214\n", + " 0.010192\n", + " 0.000159\n", + " 0.002859\n", + " 0.000089\n", " MorganFingerprintTransformer()\n", - " 256\n", + " 256.0\n", " 0.775\n", " {'fp_transformer': MorganFingerprintTransforme...\n", " 0.169585\n", @@ -362,13 +377,13 @@ " \n", " \n", " 4\n", - " 0.027070\n", - " 0.001007\n", - " 0.003137\n", - " 0.000176\n", + " 0.010103\n", + " 0.000126\n", + " 0.002868\n", + " 0.000119\n", " MorganFingerprintTransformer()\n", - " 256\n", - " 1.0\n", + " 256.0\n", + " 1.000\n", " {'fp_transformer': MorganFingerprintTransforme...\n", " 0.204831\n", " 0.546774\n", @@ -400,13 +415,13 @@ " \n", " \n", " 60\n", - " 0.113114\n", - " 0.018944\n", - " 0.029888\n", - " 0.008465\n", + " 0.100754\n", + " 0.006501\n", + " 0.025367\n", + " 0.001743\n", " MACCSKeysFingerprintTransformer()\n", " NaN\n", - " 0.1\n", + " 0.100\n", " {'fp_transformer': MACCSKeysFingerprintTransfo...\n", " -1.649022\n", " -1.943461\n", @@ -419,10 +434,10 @@ " \n", " \n", " 61\n", - " 0.102846\n", - " 0.001870\n", - " 0.025450\n", - " 0.001682\n", + " 0.118554\n", + " 0.022440\n", + " 0.036584\n", + " 0.023911\n", " MACCSKeysFingerprintTransformer()\n", " NaN\n", " 0.325\n", @@ -438,13 +453,13 @@ " \n", " \n", " 62\n", - " 0.103216\n", - " 0.001285\n", - " 0.025450\n", - " 0.001661\n", + " 0.097552\n", + " 0.001638\n", + " 0.025571\n", + " 0.001753\n", " MACCSKeysFingerprintTransformer()\n", " NaN\n", - " 0.55\n", + " 0.550\n", " {'fp_transformer': MACCSKeysFingerprintTransfo...\n", " -0.657588\n", " -0.505782\n", @@ -457,10 +472,10 @@ " \n", " \n", " 63\n", - " 0.103230\n", - " 0.002304\n", - " 0.025570\n", - " 0.001561\n", + " 0.098300\n", + " 0.001744\n", + " 0.025552\n", + " 0.001695\n", " MACCSKeysFingerprintTransformer()\n", " NaN\n", " 0.775\n", @@ -476,13 +491,13 @@ " \n", " \n", " 64\n", - " 0.104568\n", - " 0.002589\n", - " 0.025515\n", - " 0.001571\n", + " 0.098103\n", + " 0.001473\n", + " 0.025320\n", + " 0.001673\n", " MACCSKeysFingerprintTransformer()\n", " NaN\n", - " 1.0\n", + " 1.000\n", " {'fp_transformer': MACCSKeysFingerprintTransfo...\n", " -0.339715\n", " -0.266652\n", @@ -500,43 +515,43 @@ ], "text/plain": [ " mean_fit_time std_fit_time mean_score_time std_score_time \\\n", - "0 0.025447 0.002689 0.003600 0.000743 \n", - "1 0.025515 0.001279 0.003084 0.000064 \n", - "2 0.026243 0.000330 0.003101 0.000081 \n", - "3 0.026571 0.001018 0.003208 0.000214 \n", - "4 0.027070 0.001007 0.003137 0.000176 \n", + "0 0.011822 0.001013 0.003596 0.001311 \n", + "1 0.010119 0.000152 0.002832 0.000070 \n", + "2 0.010302 0.000429 0.003310 0.000967 \n", + "3 0.010192 0.000159 0.002859 0.000089 \n", + "4 0.010103 0.000126 0.002868 0.000119 \n", ".. ... ... ... ... \n", - "60 0.113114 0.018944 0.029888 0.008465 \n", - "61 0.102846 0.001870 0.025450 0.001682 \n", - "62 0.103216 0.001285 0.025450 0.001661 \n", - "63 0.103230 0.002304 0.025570 0.001561 \n", - "64 0.104568 0.002589 0.025515 0.001571 \n", + "60 0.100754 0.006501 0.025367 0.001743 \n", + "61 0.118554 0.022440 0.036584 0.023911 \n", + "62 0.097552 0.001638 0.025571 0.001753 \n", + "63 0.098300 0.001744 0.025552 0.001695 \n", + "64 0.098103 0.001473 0.025320 0.001673 \n", "\n", - " param_fp_transformer param_fp_transformer__nBits \\\n", - "0 MorganFingerprintTransformer() 256 \n", - "1 MorganFingerprintTransformer() 256 \n", - "2 MorganFingerprintTransformer() 256 \n", - "3 MorganFingerprintTransformer() 256 \n", - "4 MorganFingerprintTransformer() 256 \n", - ".. ... ... \n", - "60 MACCSKeysFingerprintTransformer() NaN \n", - "61 MACCSKeysFingerprintTransformer() NaN \n", - "62 MACCSKeysFingerprintTransformer() NaN \n", - "63 MACCSKeysFingerprintTransformer() NaN \n", - "64 MACCSKeysFingerprintTransformer() NaN \n", + " param_fp_transformer param_fp_transformer__fpSize \\\n", + "0 MorganFingerprintTransformer() 256.0 \n", + "1 MorganFingerprintTransformer() 256.0 \n", + "2 MorganFingerprintTransformer() 256.0 \n", + "3 MorganFingerprintTransformer() 256.0 \n", + "4 MorganFingerprintTransformer() 256.0 \n", + ".. ... ... \n", + "60 MACCSKeysFingerprintTransformer() NaN \n", + "61 MACCSKeysFingerprintTransformer() NaN \n", + "62 MACCSKeysFingerprintTransformer() NaN \n", + "63 MACCSKeysFingerprintTransformer() NaN \n", + "64 MACCSKeysFingerprintTransformer() NaN \n", "\n", - " param_regressor__alpha params \\\n", - "0 0.1 {'fp_transformer': MorganFingerprintTransforme... \n", - "1 0.325 {'fp_transformer': MorganFingerprintTransforme... \n", - "2 0.55 {'fp_transformer': MorganFingerprintTransforme... \n", - "3 0.775 {'fp_transformer': MorganFingerprintTransforme... \n", - "4 1.0 {'fp_transformer': MorganFingerprintTransforme... \n", - ".. ... ... \n", - "60 0.1 {'fp_transformer': MACCSKeysFingerprintTransfo... \n", - "61 0.325 {'fp_transformer': MACCSKeysFingerprintTransfo... \n", - "62 0.55 {'fp_transformer': MACCSKeysFingerprintTransfo... \n", - "63 0.775 {'fp_transformer': MACCSKeysFingerprintTransfo... \n", - "64 1.0 {'fp_transformer': MACCSKeysFingerprintTransfo... \n", + " param_regressor__alpha params \\\n", + "0 0.100 {'fp_transformer': MorganFingerprintTransforme... \n", + "1 0.325 {'fp_transformer': MorganFingerprintTransforme... \n", + "2 0.550 {'fp_transformer': MorganFingerprintTransforme... \n", + "3 0.775 {'fp_transformer': MorganFingerprintTransforme... \n", + "4 1.000 {'fp_transformer': MorganFingerprintTransforme... \n", + ".. ... ... \n", + "60 0.100 {'fp_transformer': MACCSKeysFingerprintTransfo... \n", + "61 0.325 {'fp_transformer': MACCSKeysFingerprintTransfo... \n", + "62 0.550 {'fp_transformer': MACCSKeysFingerprintTransfo... \n", + "63 0.775 {'fp_transformer': MACCSKeysFingerprintTransfo... \n", + "64 1.000 {'fp_transformer': MACCSKeysFingerprintTransfo... \n", "\n", " split0_test_score split1_test_score split2_test_score \\\n", "0 0.017975 0.394682 0.524598 \n", @@ -593,12 +608,13 @@ { "cell_type": "code", "execution_count": 6, + "id": "a6041579", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:08.447990Z", - "iopub.status.busy": "2024-04-12T12:11:08.446959Z", - "iopub.status.idle": "2024-04-12T12:11:08.691729Z", - "shell.execute_reply": "2024-04-12T12:11:08.691123Z" + "iopub.execute_input": "2024-11-24T09:28:28.324000Z", + "iopub.status.busy": "2024-11-24T09:28:28.323259Z", + "iopub.status.idle": "2024-11-24T09:28:28.574471Z", + "shell.execute_reply": "2024-11-24T09:28:28.573892Z" }, "lines_to_next_cell": 2 }, @@ -618,17 +634,20 @@ "# Best Fingerprint Method / Performance\n", "res_dict = {}\n", "for i, row in df_training_stats.iterrows():\n", - " fp_name = row['param_fp_transformer'] \n", - " if(fp_name in res_dict and row['mean_test_score'] > res_dict[fp_name][\"mean_test_score\"]):\n", + " fp_name = row[\"param_fp_transformer\"]\n", + " if (\n", + " fp_name in res_dict\n", + " and row[\"mean_test_score\"] > res_dict[fp_name][\"mean_test_score\"]\n", + " ):\n", " res_dict[fp_name] = row.to_dict()\n", - " elif(not fp_name in res_dict):\n", + " elif not fp_name in res_dict:\n", " res_dict[fp_name] = row.to_dict()\n", - " \n", + "\n", "df = pd.DataFrame(list(res_dict.values()))\n", - "df =df.sort_values(by=\"mean_test_score\")\n", + "df = df.sort_values(by=\"mean_test_score\")\n", "\n", - "#plot test score vs. approach\n", - "plt.figure(figsize=[14,5])\n", + "# plot test score vs. approach\n", + "plt.figure(figsize=[14, 5])\n", "plt.bar(range(len(df)), df.mean_test_score, yerr=df.std_test_score)\n", "plt.xticks(range(len(df)), df.param_fp_transformer, rotation=90, fontsize=14)\n", "plt.ylabel(\"mean score\", fontsize=14)\n", @@ -639,19 +658,20 @@ { "cell_type": "code", "execution_count": 7, + "id": "3ee14366", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:08.694324Z", - "iopub.status.busy": "2024-04-12T12:11:08.694077Z", - "iopub.status.idle": "2024-04-12T12:11:09.027663Z", - "shell.execute_reply": "2024-04-12T12:11:09.026932Z" + "iopub.execute_input": "2024-11-24T09:28:28.576983Z", + "iopub.status.busy": "2024-11-24T09:28:28.576761Z", + "iopub.status.idle": "2024-11-24T09:28:28.913143Z", + "shell.execute_reply": "2024-11-24T09:28:28.912512Z" }, "lines_to_next_cell": 2 }, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAABIkAAAJGCAYAAADf3NUDAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy81sbWrAAAACXBIWXMAAA9hAAAPYQGoP6dpAADQ9UlEQVR4nOzdd3gU5d7G8Xs3IQkBEgKBAKGEJsUChK4gRRAQEawcUYEoqCiCoEhROoKgIhYU5VCPBUSaooIYAUEQpamoCCgJNaEnEEoged4/eDOyZNM2ye4Gvp/r2gsy88zOb2fnmZm9d3bGZowxAgAAAAAAwDXN7ukCAAAAAAAA4HmERAAAAAAAACAkAgAAAAAAACERAAAAAAAAREgEAAAAAAAAERIBAAAAAABAhEQAAAAAAAAQIREAAAAAAABESAQAAAAAAAAREgF5ymazyWazafXq1Z4u5aq2evVqa1mjYFi7dq06duyoUqVKycfHRzabTV26dPF0WYDHtGzZUjabTaNGjfJ0KVc99s3eZe/evYqKilLFihXl5+cnm82m4sWLe7osXOU8sR2IiYmx5hsTE+O2+QK5RUgEZGDUqFHWhj2rB64eq1ev1qhRozR79myXps/uOuPs4eo8vd2PP/6o1q1b66uvvtKxY8dUokQJhYWFKSQkxNOlXZUuX6dq1aqVZfuff/7ZYZqePXvmf5FXmcuD66weBBVXj5iYGI0aNSrDoC8tCHTlcbX2w4SEBN1yyy2aPXu29u3bp8DAQIWFhSksLMzTpcFLXR60XPnw8/NTmTJl1LZtW73//vu6cOFCjp//5MmTVj8+efJk3r8AoADy9XQBQEGQ3YOXGjVqSJICAwPzs5xrXmBgoLWs89rq1as1evRotWjRwqWD9IzWldOnTyspKSnTNoULF87x/AqCKVOm6OLFi7rlllv0+eefq0SJEp4u6ZqxY8cObdiwQU2bNs2wzcyZM91Y0dUvJCREfn5+GY5PG1exYkXVqFFDoaGh7irtmpVf++aYmBiNHj1akpwGRWmB+JWSk5N14sQJSRmvL8HBwXlaq7f45JNPtH//foWEhGj9+vWqWbOmp0tCARIUFORwrJSYmKj4+HjFx8fr22+/1QcffKCVK1c6Pc7IaDtw8uRJqx/37NkzT89qK1SokDXfQoUK5dnzAvmNkAjIhri4uGy127FjRz5XAklq1KiR1y7rjNaVUaNGWQch2V2frha//fabJOk///kPAZEbRUREKCYmRrNmzcowJDp37pzmzZsnm82mihUrKjY21s1VXn0WLVqkli1bZtlu7ty5+V8MJHlu37xo0SKnw1evXq1WrVpZbbKzvlwt0vYHrVu3JiBCjr355pvpvsA7cOCAXnvtNU2ZMkVbtmzR0KFD9f7776eb1hPbgfDwcK89XgUyw8/NAAD56syZM5KkokWLeriSa0v37t1ls9k0f/586z240qJFi3Ty5Em1aNFCERER7i0QwDWH/QHyWnh4uN544w3ddtttkqQlS5Z4tiDgKkBIBOShjK45ceWF6+Lj49W/f39VrlxZAQEBCgsL03/+858sv22IjY3VY489pvLly8vf31/ly5dXVFSUdu/ena2L4yUnJ+vdd99Vq1atFBoaav2Wu3Pnzvr666+z9boOHz6sgQMH6rrrrlNgYKDDNZl69uxpXUvBGKNp06apUaNGCgoKUlBQkJo1a6aPP/44w/lcfiHXCxcu6PXXX1eDBg1UvHhxh+Wa2YWrZ8+eLZvNZn3g3bx5sx544AGVLVtW/v7+qlKligYOHGid6p8mbfmlne2zZs0at14zKLvL+MyZM/rkk0/UvXt31a1bV6VKlZK/v7/KlSunLl26ZPo+urpsLrdx40Y99NBD1rpbpEgRVapUSS1atNDYsWO1f//+dK8pbX2MiopyWJ5Xrqd///23+vTpo+rVq6tw4cIKCgpSZGSkxowZo8TERKf1XLkubN26VQ899JDKly+vQoUKWd/QX/na165dq06dOql06dIqUqSI6tWrpxkzZjg895dffqm2bduqVKlSCgwMVMOGDTV//vwMl02aH374QQ8//LAqVaqkgIAABQcHq1GjRpo4caJOnz7tdJor+85///tfNWvWTCVLlnR53atcubJatGihxMRELVy40GmbtJ+aRUVFZes5t27dqu7du1uvLSQkRDfffLOmTJmi8+fPO53mymW/atUqdenSRWXLlpWPj0+6b4W///57derUSaGhoSpcuLBq1KihF198UadPn073XJfzdN/IqcwuXB0REWG978nJyXr11VdVp04dFSlSRMHBwWrdurWWL1+e6fMnJSVp5MiRqlWrlgoXLqzSpUvrjjvuUHR0dLp5ZOTLL7/Uvffeq/DwcPn7+yskJES33nqr3nvvPSUnJ2f5ujLbjl+5zFeuXKkOHTqoVKlSKly4sK6//nqNGzdO586dczqftOsGpvXxhQsX6vbbb1fp0qVlt9sdlmt+7JsjIiKss4Eun0deXFMou8swNTVV0dHR6tevn5o0aaLy5cvLz89PJUuWVIsWLTRt2rQMr9GSF8cl+/fv14ABA3T99derSJEiVn+rX7++BgwYoJ9//jnda0pb3+bMmZPp/jUhIUFjxoxRZGSk9ROj6tWrq0+fPvrnn38yrCk7+9IrX3tsbKx69+6tihUrKiAgQFWrVtVLL71k/URckrZv366HH35YFSpUUEBAgKpXr65x48ZleQ2cmJgYPfvss7r++utVtGhRBQYGqmbNmurfv7/27t3rdJqcbjMzc/nyiIuLU9++fa33uEyZMnrooYeydaZLfm4L8krdunUlKcP9rLPtQMuWLVW5cmXr78qVKzusl1ee5bdjxw49/vjj1joVEBCgChUqqEmTJho2bFi6ZZnZsXlur0/myrEGkG0GgFMjR440kkxOukla+1WrVjkM37NnjzVu2bJlpnTp0kaSCQwMNP7+/ta4oKAgs23bNqfPvX79elOsWDGrbeHChU3RokWt6T799FNr3J49e9JNHxMTY66//nqrjc1mM8HBwdbfksyTTz6Z6euaPn26CQsLM5JMQECAVU+aHj16GEmmR48epmvXrkaSsdvtJiQkxNhsNut5oqKiTGpqarr5tGjRwkgygwcPNjfffLORZHx9fa3p05brqlWrMnxvZs2aZSSZSpUqmY8++sgUKlTISDLBwcHGbrdb011//fXm1KlT1nR79+41YWFhpkiRIkaSKVSokAkLC3N4zJs3z+nyyY6s1qfsLuO013f5exgYGOjwPj733HNO5+Hqskkze/Zsh/fR39/fBAUFOcx71qxZVvu05Zb23EFBQQ7Lc+/evVbb+fPnO/SFYsWKOfxdoUIF88cff6Sr6fJ14bPPPrNeU1BQkAkICDAtWrRI99qnT59u7Ha70z4wZMgQY4wxI0aMsNbfK9u89957TpdvSkqK6devn0PbokWLGh8fH+vvGjVqmJiYmHTTpvWd7t27m3vvvdeh79jtdoflmpXL34s5c+YYSaZVq1bp2sXExBibzWaKFStmkpKSrP7Xo0cPp887efJkh/c/ODjYWt6SzE033WQOHjyYbrrLl/2UKVOs50ib/vL5vfXW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NjZWPj4+WrZsmTp06JDuG05nZwbkJT8/P91zzz16//339dtvv+nIkSOaNm2aSpQooX379qlHjx45er7L14HMfg6RNs7X19flb+ryU1o/89ZTu8uWLav27dvr7NmzGj58uKTs/dRM+vc9Onr0qM6fP59hu7T3yJV+ffkBsSvbOW/oG94ibVlmthzPnz+f4Zml7l6Xjx49mulPNthfOHfhwgUtWrRIkvTOO+8oKirKYX8vyeHsufzSrFkzTZw4UevWrdPJkye1dOlS3XjjjTp79qweffTRbJ1Jk+by453s7A8k71wvvHF/kNlx2eXjLl+e3vg6slKpUiVJcvjZXF6y2+1q166d3nzzTW3atEnHjx/XRx99pIoVK+rEiRPq1q1bjn+CNmzYMH322Wfy8fHR/PnzrZ9GO1MQ3xMUTIREgJfz8/PT9ddfL0mZfiOQ0biIiAjrVOIvvvgir8tzatOmTdb1ja60e/du6wCvQYMGbqknJ9JO7fbGMw327dsn6dKHwIxOHU+7NoC7lCxZUk888YQmTpwo6dK1ZXJyYevIyEhrmaddL8eZtNdVp06dDC++7klp11z49ttvde7cOQ9X41zaBayTk5MVGhqqu+66K1vTpfXTixcvas2aNRm2S3uPMrp+R2bq1atnnfniynbOG/uGp6RdwySz9+qHH37QxYsXnY5LW5eXLVuW98U5cfHiRa1du9bpOGOM9Tq8eX8huX+fceTIEWtbU69ePadt1q1b59btUUBAgO666y4rvDp37pzWrVuX7en9/Px00003Scre/sBut+fZNXvyUlofiouLy/BaWu62atWqLMeVKFHC+qmZ5P5tQV5IO77M6LqCzuSmHxcrVkzdunWzLvIdHx+fozOqZ86cqVdeeUWS9NZbbzlcMN2ZgnCsgasDIRFQANx3332SLt154u+//043/tixY5o2bVqG06ddHHHGjBnaunVrpvPKiwtznj17Vq+99prTcePGjZN06WAk7eKT3iTtgtuXX9fEWwQHB0v69257V9q/f7/eeuutfJl3ZmeQSHK4M05OrqFQvHhx66Do1Vdfdfr7+V9++UULFy6UJD344IPZfm53evTRR+Xr66ujR4+muxvJlZKTkzMMUfNTp06dNGjQID333HOaMmVKtsO2m266yfpmc9y4cU4vVv7VV19p48aNklx7j0qUKGHdIef11193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uB599FFt2bJFVatWtdoWL1483fTlypXTunXr9Mknn+iuu+5S2bJldebMGSUnJysiIkKdOnXSlClT9P333+dJvZ988oneffdd1atXTxcvXlSRIkXUtGlTzZ07V3PmzMmz27rmNV9fX0VHR6tXr16qXLmykpKSFBsbq9jYWK84bffJJ5/Ul19+qZYtW6po0aK6ePGiwsPD9cwzz+iXX37RjTfemC/zveuuuzR37lxFRUWpTp06Cg4OVkJCgooVK6ZGjRpp7Nix+v3331WzZk2Xnr9r1676/fff9cQTT6hq1ao6f/68fH19VbduXY0ePVrbt29XrVq18vhV5S2bzaYxY8bo119/1VNPPaVatWrJx8dHCQkJCgkJ0c0336xBgwZp/fr11hkABcmAAQO0adMmPfzww6pQoYLOnDmjwoULq0mTJnrjjTf0888/Z3rb9ex49tlntXr1at1xxx0KCQnRuXPnFBERoZdeekk//vij9Q24s22cp/qGNypTpox+/vlnDR8+XDVq1JDdbpevr6/uuOMOfffdd+rdu7cSEhIkOV+Wfn5+mj59utavX6+ePXuqatWqSklJ0enTp1W6dGm1bNlSI0aM0K+//prj25w78/TTT2vFihVq37697Ha77Ha7atasqTFjxmjDhg0qWbJkrueRX6ZOnapRo0ZZ69fevXsVGxub7xeMlqT69evrp59+0gMPPKDQ0FClpqaqWLFieuCBB7R+/Xo98sgj+TLf8PBwff755xowYICaNGmismXL6vTp0/L19VXt2rX19NNPa/v27dZPH3Pqhhtu0O+//65Ro0apbt268vX11fnz51W1alU9+eST+v33311+bnfq0qWLdu/erZEjR6pRo0YqWrSoTp48KX9/f9WpU0e9evXS4sWLNWjQoHyvpXLlytq6dauefvpplSpVSsnJySpdurQefPBBbd26VR07dnQ6nbu3BVlJTEy0flYcHx+vo0ePKjAwUPXr19cLL7yg33//3aWfpg4bNkxvvvmmGjRooEKFCmn//v2KjY21bnjQsGFDffrpp+rTp4/q16+v0NBQJSYmKiAgQHXr1tULL7ygP//806Wz8U+cOOHwmq58pG2r01ztxxrwDjaTdsQFoECbPn26Hn/8cVWpUsXpdYvcoWfPnpozZ4569OiR4a1QAcBVDz30kD7++GM9+uij1oVCkXO7du3S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8olLIVGfPn20evVq+fj4SJJatmypefPmqVKlStq+fbvCwsL01ltvqXfv3nlaLAAAAAAAAPKHrysTBQUFqXHjxg7D7r//ft1///15UhQAAAAAAADcy6UziQAAAAAAAHB1yVVItHjxYj3wwAO66aabVK1aNWv4jh07NGnSJB04cCDXBQIAAAAAACD/ufRzs9TUVD344IP67LPPJEmFCxfW2bNnrfEhISF68cUXlZKSoqFDh+ZNpQAAAAAAAMg3Lp1J9MYbb2jBggV64okndOLECT3//PMO48PCwtS8eXN9+eWXeVIkAAAAAAAA8pdLIdHs2bPVsGFDvfvuuwoKCpLNZkvXplq1atqzZ0+uCwQAAAAAAED+cykk2r17t5o3b55pm5IlS+rYsWMuFQUAAAAAAAD3cikkKly4sBISEjJtExsbq+LFi7vy9AAAAAAAAHAzl0KievXqacWKFTp37pzT8cePH9fy5cvVpEmTXBUHAAAAAAAA93ApJOrXr5/279+ve++9V/v373cY9/fff+vuu+9WQkKC+vXrlydFAgAAAAAAIH/5ujJR586dNXjwYE2cOFGVKlVSkSJFJEmlS5fWsWPHZIzR8OHD1bp16zwtFgAAAAAAAPnDpTOJJGnChAlasWKF7rzzTgUGBsrHx0epqalq3769vv76a40ePTov6wQAAAAAAEA+culMojRt27ZV27Zt86oWAAAAAAAAeIjLZxIBAAAAAADg6pGrM4lSUlK0f/9+HTx4UBcuXHDa5tZbb83NLAAAAAAAAOAGLoVEqampGj9+vN58800dP34807YpKSkuFTZ16lS9+uqriouLU506dfT222+rUaNGTtsuWrRI48eP1+7du3XhwgVVr15dzz33nB555BGX5g3Afex+Aao0eJmnywAAAACAa55LIdHQoUP16quvqnTp0oqKilLZsmXl65urk5IczJ8/XwMHDtS0adPUuHFjTZkyRe3atdNff/2l0qVLp2tfokQJvfjii6pZs6b8/Py0bNkyRUVFqXTp0mrXrl2e1QUAAAAAAHC1cinZmTNnjmrUqKGff/5ZRYsWzeuaNHnyZPXu3VtRUVGSpGnTpunLL7/UzJkzNWTIkHTtW7Zs6fB3//79NWfOHK1bt85pSHT+/HmdP3/e+jsxMTFvXwAAAAAAAEAB49KFq0+fPq2OHTvmS0CUnJyszZs3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+Bs2bJhh3fmlbdu2at++vbp3765SpUq5dd55yVMfNLOLvpEz9I284+1946+//lKtWrVks9mUkpLilnm+/vrrevHFF3XhwgVJUkBAgF588UUNHTo03Xo2evRojRkzxm21jRkzRi1btnQ4/f7MmTPq0aOHFi1aJOnfi6iXKFFCs2fPVseOHd1Sm+R43Yt69eqpV69e6tatm4KCgtxWQ17xxLqXE57suwUxRL2SN4WABLwArhkGuMKOHTuMzWYzdrvdrfP99NNPTcmSJY3dbjd2u92UL1/efPTRR07bjho1yq31zZkzx/zyyy8Ow1JTU83zzz9v/Pz8rJrtdrupWbOm2bRpk9tqM8ZY75efn5+59957zddff21SU1PdWkNe8NS6l130jfToG+7h7X0jISHBzJ4928yePdst81uxYoWx2WymePHi5oknnjB9+/Y1ZcqUMXa73XTs2NGcO3fOob27+4XNZjOjR492GPbII48Ym81matasaV5++WXz/vvvm6ioKOPj42MKFy5s/vrrL7fW16xZM3PjjTda61WRIkVMjx49zJo1a9xWR144c+aMWb16tVm9erWnS3HKU333tddeM/7+/tb2NzAw0Lz88stOt3/u7h854e7lN3r06HR9ICkpydx3333WskyrJzQ01CxbtswtdaVJm7fdbjf169c37733nklISHBrDXnp0KFDJioqyjz66KNun+/ChQvN0qVLzfHjxzNst3r16nTbck/ZunWreeedd8zEiRPN/PnzTWJiottrqFatmunbt6/ZunWr2+ed1/bu3WtatWplWrdu7elSvBIhEdI5evSoGTlypBk1apTb5rlx40bj4+Nj/Pz8TLt27cydd95pAgICjN1uN08++WS69t5wwD9w4EBjs9lM6dKlTe/evc3QoUNNq1atjM1mMyVLljQHDhxwa3033HCDCQ0NtQ4gKlasaEaOHGliYmLcVkduufuDZk7RN9Kjb7iHt/cNd+vQoYMpUqSI2blzpzUsMTHRPPjgg8Zms5m2bduas2fPWuM83S/+/vtvY7fbTdOmTc2ZM2cc2i5cuNDYbDbzxBNPeKS+jRs3mt69e5ugoCCrj9SoUcNMmjTJxMfHu62mq5Un+q63h6g54e4QkIDXvTwRonpzgEpI6T7e/uWbpxESwSvce++9plChQmbdunXWsNjYWHPrrbcau91uevTo4bDx9vQB/4EDB0yhQoVM7dq1TVxcnEPbKVOmGJvNZp577jm315ecnGzmz59v2rZta3x8fIzNZjM+Pj7m9ttvN59++qlJTk52W03IG/SNvKmPvnF1CQ0NNd27d3c6bsiQIcZms5nbbrvNCoo83S9mzJhh7Ha7+e6775y2b968ualWrZq7ynP6QTgpKcnMmjXLNGvWzOEMvLvvvtt8+eWXBfIMvGuVt4eo3oyA1704C9URIaX7ePtZqJ5GSASvUKZMGXP//fenG37hwgXTrVs3Y7PZzMMPP2wdpHp6o/2///3P2O128/nnnzttHxkZaWrXru2u8pzuVGJjY83IkSNNxYoVHb51GDBggNm+fbvbakPu0Ddyh76Rd1JTU82ePXvMtm3bzLZt28yePXtMSkqKR2rx9/c3w4YNy3D8Sy+9ZGw2m2ndurU5c+aMx/vF+PHjjd1uNydPnnTavn///qZw4cLuKs9pv7jcX3/9ZQYNGmTKlClj9ZEKFSq4rT7kjreHqN6MgPfq5u0BKiElvIXd09dEguekpqYqISFBCQkJSk1N9Wgtx48fV/Xq1dMN9/X11Ycffqju3bvro48+0sMPP+zxWiVp//79kqSmTZs6Hd+kSRPFxsa6s6R0KlasqFGjRikmJkbLly/Xvffeq1OnTmnKlCm66aabdPPNN3u0Pm9G33AdfePqM3/+fLVp00aBgYGqWrWqIiMjFRkZqapVq6pIkSJq27atPv30U7fWFB4ergMHDmQ4fuzYsXrxxRe1atUq3XHHHTp9+rQbq0svODhY0qULRjvj4+OT4ThPuO666zRp0iTt379fixYt0h133KFDhw55uqwMDRo0SFWrVvXY/I0xiomJ0S+//KJffvlFMTExHt0enzp1KsNbYk+YMEEvvviivvvuO3Xs2FFnz551c3UFS3x8vCQpMjLS6fjIyMhMt0XuEBgYqJ49e2rt2rXasWOHnnvuOZUoUUJLlixRp06dVKlSJY/W501+/vln3XvvvQ7HVcWKFdPHH3+swYMH69tvv9Wdd96pc+fOebDKf61evVqS9PLLL6tw4cIO4+655x41a9bMYxd0b9SokT744AMdOnRIM2fO1M0336ydO3dqyJAhqlChgu655x599dVXMtwjq0Dy9XQBcK+NGzdq+vTpWrNmjcNBjM1mU+XKldWyZUs99thjatKkiVvrKlOmjI4cOeJ0nM1m06xZs2SM0f/+9z+lpqaqWrVqbq3vSmkb6iJFijgdX6RIEa/4wC5dWn633367br/9dh0/flxz587VjBkztHHjRk+X5tTEiRO1YsUKfffdd26dL30jb9A38o+7+8bFixd1//336/PPP5cxRjVq1FD16tWtO2AlJiZq165dio6O1nfffadPPvlECxYskK9v/h9a3HDDDVkeGI8dO1bSpYPr9evX53tNV1qyZIliYmIkSQcPHpQk/f33307vSLR//36Fhoa6sbrs8fHxUZcuXdSlSxevDomOHj1qLWt3mj9/vqZPn64ffvhBycnJDuP8/PzUrFkz9e7dWw888IBb68pOiCpd6ht33HGHGjRo4K7SXDJo0CAtWrRIf//9t9vnXVAD3gkTJuiLL77QjBkztHz5co/WZIxRbGysEhISJF1aphUrVvTIcssqQPX19dXLL7+sjh07atmyZW6uLr3shJQffPCBO0tKJy2k7Nmzp3bu3Kn//ve/+t///qclS5Zo6dKlCg8P1969ez1aI3KOkOga0r9/f73zzjsyxqhIkSKqVauWw8H+nj17NGPGDM2cOVPPPPOMpkyZ4rbaatasaaXlzthsNs2ePVuS9L///c/ttxyV5FDfzp07JUkxMTGqVatWurb79+9XyZIl3VVatpUoUULPPvusnn32Wf3888+eLsepHTt2aM2aNW6dJ30jd+gb7uHuvjFp0iQtXbpU//nPf/TKK6+oYsWKTtvt3btXQ4YM0fz58/Xqq69q6NCh+V5bx44d9cUXX+jLL7/M9NbxY8eOlc1m07hx46xbvrvLtm3btG3bNodhS5YscRoSbdiwQTfeeKN7CnNR2bJlPV2C1/DmAFUqGCFqTrg7BCTgzRveGKIWtACVkDJ/eTKA9naERNeIadOm6e2331bz5s01duxYNWvWLN1GJTU1VWvXrtXw4cP19ttvq1atWnriiSfcUl+HDh00cOBArV27Vs2bN3faJu3DsM1m09y5c91+wL969ep0H9aXLVvm9IPwpk2bVKNGDTdV5pqGDRt6ugSvQN/IPfrG1Wnu3Llq2rSpPv7440zbVaxYUR9//LH27NmjOXPmuCUkuu+++6xQNytjxoxRlSpV3Pozxz179jgdHhgYmG7Ytm3bVLVqVd177735XZZl1apVioiIcNv8curRRx/NUft169blUyXOeXOAKhWMENWbEfDmjjeHqAUhQCWkdB9PnYVaEBASXSOmTZtmbRgz2gjb7Xa1aNFC0dHRioyM1Hvvvee2D8IPPPCA4uPjdezYsUzbpf28JiIiwq0H/KtWrXI6vFSpUumGbdmyRRcuXFC7du3yuyzLrFmznO48vMGYMWNy1P7KA7P8Rt/IHfqG67y9b8TGxuqee+7JdvuWLVu67Sy7EiVK5KgP9uzZM/+KcSIn1wCpW7duhv0ov7Ro0cKt88uptNA7J9eycGfI4c0BquT9Iao3h4AEvLnnzSFqQQhQCSnhDWyGq0ldEwIDA9W/f39NmDAhW+2HDBmit956S2fOnMnnynC1s9vtLh3sp6Sk5GNV/6JvwFO8vW+UK1dOzZo1y/ZFqe+//3798MMP1jefgKuKFy+u8PBwzZw5M1vtx44dq6+//tptfaNw4cIaMGCAxo8fn632Q4cO1ZQpU7hI9P/z9m0fcqdmzZoqWbKkfvjhh2y1b9q0qU6cOKEdO3bkc2WXbgayYMEC1ahRQy1btsyy/ezZsxUbG6uRI0fme22SMgxrAwMD0335tm3bNg0YMECPPPJIjoNXV61Zs0YRERFeezH0nC6HtWvX6p9//mHb4gRnEl0jihQpkqPT/Q4dOpStb6CArAQGBio8PDzbZ0289957Wrt2bT5X9S/6BjzF2/tGhw4dNGfOHE2fPl29e/fOtO3777+vxYsXKyoqyk3VXZKYmKjY2FhVqVLFoV+uWLFCq1evVqFChdShQ4cM77Z3rXr55ZfVrl07j19vIyORkZH6+eef1ahRo2x9i+/szMX8FBISot27d2e7/e7duxUSEpKPFRUsQUFBLoWAKBg4C9V1nIWaO95+FmpBQkh0jWjVqpXmz5+vbt266fbbb8+07fLlyzV//nx17tzZTdU5OnXqVLqL7/7xxx9au3atChUqpDZt2mR46uq16qOPPtJtt92mMmXKeLqUdOrWravff/9dXbt2zVb75cuXu/WDMH3j6kbfcN3LL7+slStX6sknn9SkSZPUtm1bVa9e3bqQZkJCgnbt2qWVK1fqn3/+Ufny5TVu3Di31TdhwgSNHj1aFy5cUGBgoN5++2317NlTzz77rN5++23rIPHll1/WoEGD9Morr7ittpyIi4vTsGHDZLPZNGPGDLfMc/jw4RoxYoRuuukmPfbYY3r44YdVvHhxt8w7OyIjI7VmzRrt2LHD6bXNPK0gBKiS94ao3h4CejNvD3glQlR4DgF0HjK4JuzcudMEBwcbu91u2rRpYyZOnGgWLVpkoqOjTXR0tFm0aJGZOHGiue2224zdbjchISFm586dbq3xww8/NGXKlDF2u91ERESY5cuXG2OMef31142vr6+x2+3Gbrcbf39/M3XqVLfWlhNHjx41o0ePNmPGjHHbPG02mylUqJDp0qWL+eKLL0xKSorb5p2VZ555xtjtdrN79+5ste/Zs6ex2+35XNW/6BvuQ99w5O19wxhj4uLiTPfu3U1AQICx2WzGZrNZ61va3/7+/qZHjx7m0KFDbqsrOjra2Gw2ExYWZu655x5Trlw54+fnZz766CNjt9vN008/bZYuXWreffddU6FCBWO3283KlSvdVl9O7Nixw1qu7mKz2UzRokWt+RYuXNg89NBDZtWqVW6rITPff/+96dmzp9m8eXO22q9bt87Mnj07n6v616FDh6z1qlq1aqZPnz5m8uTJZsaMGWbGjBlm8uTJpk+fPqZatWrGbrebihUrmri4OLfVZ4wx48ePN/7+/sZut5uiRYuaWbNmGWOM6d+/v0P/tdvtZvDgwW6t7bnnnjN2u9388ccf2WrviW1fdhw6dMhERUWZRx991G3zTHvP6tata95++21z4sQJt807ux599FHj4+NjPvjggyzbTps2zfj4+JhevXq5obJ/JSQkmF9//dWcPn3aYfjy5cvNkCFDzPDhw8369evdWlNBMG7cOPPzzz97uowMtWrVyhQtWtSkpqZmq723blu8ASHRNeT33383t956a7oD/SsP+Fu0aGF+//13t9a2adMm60A1MjLSFClSxBQtWtSsXLnS+Pj4mI4dO5o333zTvPDCCyY4ONj4+PiYn376ya01ZpenDvjT3kO73W7Cw8PNiy++aP7++2+31ZCRL774wrRs2dKsW7cuW+2XLFliRo0alc9VOaJvuAd9w1FB6BtpTp06Zb755hszdepUM378eDN+/HgzdepU880335jExES313PnnXea4sWLW8HU0aNHTalSpUxQUJB54YUXHNrGxMQYf39/c//997u9zuxISEgws2fPdmvIYbPZzOjRo83mzZtNnz59TPHixa0+UrVqVTN+/Hhz8OBBt9VTEHlrgGqM94eo3h4CZhcBr3PeHqJ6c4CaE4SU6V0t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c0+227ds2dKrz7BDweLtIao3I+C9unl7gEpICW/hfT9oBLzQzp071bFjx2wl9YUKFVLHjh21c+dON1QGeBZ9A0gvJCREu3fvznb73bt3KyQkJB8rApAdsbGxatGiRbbbt2zZMt3Pl+C9rrvuOt17771ZBkTSvwFqTgL/3Lo8pMwoIJL+DSkbNWrk1p9zzZo1S3fddZfb5gfPISQCsqFIkSI5uq34oUOHVKRIkXysCPAO9A14u7i4OD366KN67LHH3DbPDh06aNGiRZo+fXqWbd9//30tXrxYHTt2dENlOeeJ5Qd4CgEvPImQEt6Cn5uhwDl27JimTp0qm82m4cOHu2WerVq10vz589WtWzfdfvvtmbZdvny55s+fr86dO7ultpzyxPKDe9A3coe+kfciIiJ06623erSGhIQEzZ49WzabTTNmzHDLPF9++WWtXLlSTz75pCZNmqS2bduqevXqCg4OtmratWuXVq5cqX/++Ufly5fXuHHj3FJbTnli+eUFb1j3CoK4uDgNGzaswL2/+aVDhw6aM2eOpk+frt69e2faNi3g9dbbZfPeFjyElPAWNmOM8XQRKBimTJmiN9980+3XPLnSX3/9pVq1aslmsyklJcUt89y1a5caNmyoU6dOqXXr1hke8H/zzTdatWqVgoODtXHjxgwvhOdJnlh+ueUt615GvKU++kbu0DdyJjExUWfPnlWpUqW88na8aRITE7V48WJJl66n4C7x8fF64YUX9Omnn+r8+fOSJJvNJunSbY2lS3epSbv2RJkyZdxWW054avllpqCse5mZM2eOZs+e7fHrARXE7Z6Uf8svLi5OjRo10oEDB1SlSpVsBbw//fSTR+6AlZWC+t5K0ujRozVmzBiP1u2JkO2xxx7TnDlz9N5772UrpHz66acVFRWVrbNW3a2ghpTesO55A0IiFDjHjh3T22+/LZvNppEjR7ptvn/88Yf69OmjtWvXSvr3YD9NWle69dZb9e6776p27dpuqy0nPLX8kP/oG7lD33B04MABxcbGqkmTJg4fxN9//329/vrr+vvvvyVd+snhPffco0mTJql06dKeKtdrnT59Whs2bNCuXbuUkJAgSQoODlb16tXVpEkTFStWzMMVep+rYd07c+aMChUqlK1rn3iSN4aAkmeXHwGv53lDiOqJkI2Q0vO8Yd3zBoRE14ikpCQlJCSo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", + "image/png": "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kmk7d167Y59w6NAht+tzxXHdt7vuukvXXntthvHFixfXoEGDJEnff/+9EhMTXT5PdvelTz/9tMvtTceOHa3/P//88y6vgehoc3kfXLFihbZv366IiAj169cv03n37t1bUuZ9SMp8m5lTd999t2rXrp1heJkyZfTII49IUoZr7nljW3Al6W95n5CQ4LQtPnr0qA4cOGDt5/JS8eLFrW1UXq/z6f3xxx+6++67dfHiRd17770aMWKE0/i8OtYAroSQCMgGc+msO5cPx4Xysqt58+Yuh/v7+6t06dKSLn1gckhJSdEff/whSYqJicn0eTO7he6BAwes21o/+OCDioqKcvkoW7asdaG7zG6Dff3112f94v5fkyZNFBoa6nJcjRo1rAO3DRs25Go+WSlZsqTLQE2SypUrZ/0/Ly9Emxey89oTEhI0YsQItWzZUqVKlZK/v791gcM6depIunQR38xemzvLplq1aqpVq5YuXLig5s2b65VXXtGWLVuy/LCdHZs2bbIO6Nq3b59puw4dOki6dDCeWTCbnWVXsmRJVatWzeW4yMhISVKdOnVUtGjRLNtcvmxXr14tSfrxxx8z7WNRUVGaPn26pMz7WNOmTa0PZXmlZcuWqlWrlvbs2WNdtDgnF6x29FN/f/8st0GO9yizfl2kSBE1atTI5bjNmzdb64E72zkHb/SNyy1dujTT/YWr8DErme0v0teTfn8hXepTknTjjTe6/GArXeor/v7+Lsc51uWpU6dmuS4vXrxYUu73F/7+/pmGf3a73XrPM1uvqlevrjJlymRrXlnJ6b7ZE7KzDFNSUjR58mTdfPPNKleunIKCgpwuenvkyBFJcrqpwOXcee233nqrJKlPnz565plntHz58lx/6ZKSkmIFLtnZH6SlpVnr++Wyu/45wu3LObb10qXtclZtMtsfJCYmqly5cpn2of79+0vKvA9ltc3MqXbt2l1x3PHjx7V79+4Mr8NT24LsmD59utM29eLFi9q/f78mT56shIQEDRw4UPfee2+eB0VFihSxvkjq1KmThg8frl9++SXTi3a7IyEhQV26dFFiYqJatWpl7afTy6tjDeBKXB8hAMg3xYsXz3Sc46A9/YfgEydOWB/E039IuVz58uVdDj948KD1/2PHjmWrxswO9LJ7IJ5ZLenH79+/3zp4dXc+WcnOcpaU4zPB8tuVXvvatWt1yy236NSpU9Ywxx1TbDabUlNTrfc5KSlJERERGZ7DnWXj5+en2bNn6/bbb9fu3bv1/PPP6/nnn1dISIhatWqlO+64Q3369HF5JlBW0q8DWa03jmDx4sWLOnHihNMBvEN21pvsvPac9lHpf/0sKSkpW2eL5LaP5VRsbKwGDx6s6dOnq1mzZvrqq6/k5+dnfZOdFcd7FBER4fIbdwfHe5RZvy5VqpTLs0WkS98AO7iznZO81zfykzvromNZZrUcg4KCFBERocOHD2cY51iXT58+neldBdPL7bp8pfXK8Z7n5/5Ccm9Z57crvbYjR46offv2Tmf5BAcHKyIiwjrj7+jRo0pLS8tyu+TOa3/11Ve1Y8cOLV26VBMmTNCECRPk5+enBg0aqEuXLnrooYeueCxwufTHO9nZH0i5Xy8ye+3p+/uV2mS2P7hw4YISEhKuWMO5c+dcDs9qm5lTWS3P9OOOHDli3e3L09sCd/j5+al8+fJ6+OGHVa5cOXXr1k2zZ89W586ds7V/y4mPPvpI3bp106+//qoxY8ZozJgxCgwMVNOmTXXbbbfpwQcfVMmSJd167nPnzum2227Tnj17VKVKFc2bN8/ldjGvjjWAK+FMIqAAyexb4aykP9Pjzz//zPKsKMcjs9ttZuenPHnBU/PxRVm99osXL6pnz546deqUGjRooO+++06nT5/WP//8o4SEBB0+fFg///yz1T6vv0mrX7++tm3bpi+//FIPPfSQrr32Wp07d06LFy/WY489plq1ann85xjpeXO9cfSzwYMHZ6uPZXbr3/x6Dffff7/8/Pz09ddfa/LkyTp37pw6deqksmXL5sv8XMnua3NnO+ftvuGL3FmO0v/W5ffffz9b6/Llty53YH+Re1d6bU8//bR+//13lSpVStOmTdOhQ4d07tw5HT16VIcPH9bhw4etsDCv1/kSJUrop59+0sqVKzVo0CDr7LSNGzdq9OjRqlGjRpY/Pc1vvrA/aN68ebb6UGbvjbfX7YK2LejatasVSH366ad5/vyVKlXSpk2btHDhQg0cOFCNGzdWWlqaVq9erUGDBql69erWz6Zzwhij3r1765dfflFYWJjmz59vncF3ubw61gCuhJAI8HElS5a0drDpzwq63IEDB1wOj4qKsv7vqdNOM6vl8vH5+e1SYbR27Vrt2bNHfn5+mj9/vjp37pzhG05XZwbkpcDAQN1xxx364IMP9Pvvv+vo0aOaPHmySpYsqX379qlPnz45er7060BWP4dwjPP393f7m7r85Ohnvnpqd9myZdWpUyedO3dOw4YNk5S9n5pJ/3uPjh07puTk5EzbOd4jd/p1+gNid7ZzvtA3fIVjWWa1HJOTkzM9s9TT6/KxY8ey/MkG+wvXLly4oK+++kqS9O677yo2NtZpfy/J6ey5/HLDDTfolVde0apVq3Tq1Cl98803uu6663Tu3Dk98MAD2TqTxiH98U529geSb64Xvrg/yOq4LP249MvTF1/HlVSuXFmSnH42l5fsdrs6duyot956Sxs2bNCJEyc0a9YsVapUSSdPntS9996b45+gvfDCC/riiy/k5+enOXPmWD+NdqUgvicomAiJAB8XGBiounXrSlKW3whkNi46Oto6lfg///lPXpfn0oYNG6zrG11ux44d1gFekyZNPFJPTjhO7fbFMw327dsn6dKHwMxOHXdcG8BTSpUqpYcfflivvPKKpEvXlsnJha0bNWpkLXPH9XJccbyu+vXrZ3rxdW9yXHNh8eLFOn/+vJercc1xAeuUlBRFRESoW7du2ZrO0U8vXryo5cuXZ9rO8R5ldv2OrDRs2NA688Wd7Zwv9g1vcVzDJKv3avXq1bp48aLLcY51ef78+XlfnAsXL17UypUrXY4zxlivw5f3F5Ln9xlHjx61tjUNGzZ02WbVqlUe3R4FBwerW7duVnh1/vx5rVq1KtvTBwYGql69epKytz+w2+15ds2evOToQ4cPH870WlqetnTp0iuOK1mypPVTM8nz24K84Di+zOy6gq7kph8XL15c9957r3WR74SEhBydUT1t2jS9/PLLkqS3337b6YLprhSEYw0UDoREQAFw1113Sbp054mdO3dmGH/8+HFNnjw50+kdF0ecOnWqNm/enOW88uLCnOfOndPrr7/uctzYsWMlXToYcVx80pc4Lrid/romviIsLEzS/+62d7n9+/fr7bffzpd5Z3UGiSSnO+Pk5BoKJUqUsA6KXnvtNZe/n//111/15ZdfSpJ69uyZ7ef2pAceeED+/v46duxYhruRXC4lJSXTEDU/de3aVc8995yeeeYZTZw4MdthW7169axvNseOHevyYuXfffedfvnlF0nuvUclS5a07pDzxhtvuPwmdsWKFZmGCd7sG77Gsb+Ij493+ZMLY4xeeumlTKd/6KGHJElbt27V+++/n+W8kpKS8uTCrePGjVNaWlqG4TNnzrQCwB49euR6Pnkt/Q0aPL3PCA0NtYLVX3/9NcP4ixcvaujQofky74sXL7p8vxzc3R9I0r/+9S9J0hdffKGtW7dmGH/mzBm9+uqrkqRbbrnF6vu+pG3bttYF8J9++ukr9hFPXBB97ty5+uuvvzIMP3bsmD744ANJGfuYN7YFubFs2TLrzmM5CZWz04+v9NrcWed/+ukn685yAwcO1GOPPXbFaQrCsQYKB0IioAAYMGCAIiMjdf78eXXq1EnLly+3vu3YsGGDOnTokOm3wtKl29ted911On/+vNq2bat3333X6WyPU6dO6fvvv1fv3r1zdYtph7CwMI0ZM0bjx4/XP//8I+nSgciTTz6pmTNnSpKGDRum4ODgXM8rrzluufvHH39ozZo1Xq7G2Q033KCiRYvKGKN77rlHf//9t6RLPyn44Ycf1KZNG7evQ3Ils2fP1vXXX68PPvhAu3btsoY75u24rXzLli0VHh6eo+ceO3asAgICtGPHDnXs2NH6Fi4tLU3fffedbrnlFl28eFHVqlXL89vD55Vq1apZP+N69dVX1bt3b6cPOBcvXtSWLVs0evRoVa9ePcvbUeeXgIAAvfrqq3r99ddd3ho9K44zxVauXKm77rrLOpX/woULmjVrlhUMtWrVyrr1bk6NGjVKNptNW7duVbdu3bR9+3ZJl5bdV199pTvvvDPTdcubfcPXtG7d2grg+/fvrxkzZlgh7/79+9WrVy+tXLky04vMx8TEWD9FfPzxx/X000879fnk5GT9/PPPGjRokCpXrpzphYOzKyQkRKtWrdK9995rnQVw/vx5ffjhh3r00UclSbfddlumd6Hypmuuuca6G+FHH33k0bOJihUrZp1VEBcXp59++skKbrZu3apbbrlFGzZsyNEZFdm1f/9+1ahRQ2PHjtXmzZudjj9+++033XfffZIunc2R1d0KXXn00UdVpUoVXbhwQZ07d9b3339vva7ff/9dHTt21O7duxUUFGR96eRr/P39NXnyZPn7+2vVqlW68cYbtWTJEqcLXO/atUuTJ09W06ZN9d577+V7TcHBwerUqZMWL15srafr169X+/btdezYMRUvXtzajzt4elvgruTkZH3zzTfWfs3f318DBw7M9vQlSpSwzkCdPn26y+PpNWvWqF69enrzzTf1559/WuukMUZr1qyxtlUVKlSwzobLyvbt23XnnXfqwoULuuWWWzRhwoRs1VoQjjVQSBgALo0YMcJIMjnpJo72S5cudRq+e/dua9zu3bsznb5y5cpGkpk+fXqGcStXrjTFihWznickJMT6u0SJEmbu3LnWuEOHDmWY/sCBA6ZFixZWG5vNZkqUKGFCQ0OtYZJM9erVs/26LtenTx8jyfTp08f06NHDSDJ+fn4mPDzc2Gw263l69+5tUlNTM0wfExNjJJkRI0ZkOZ+lS5dm+t5Mnz7dSDKVK1fOdPqs3o8LFy6YmjVrWuPDw8NN5cqVTeXKlc3cuXOzrCsrV1qfsruM33//faf3q1ixYiY4ONhIMhEREebbb7/N9LXlZtk4pnU8goKCTKlSpYzdbreGlStXzvz5558ZnjOr9dph9uzZJjAw0Hqu0NBQ63VJMhUrVjT//e9/M0yX1bqQ09fueI9iYmIybZN+Hb9cWlqaGTZsmNO6XqRIEVOqVCnj5+fntPxWrVqV7efNKcc8slrerjj6X2Y1TJgwwem1lShRwuk9u+6668yBAwcyTJedZe/w5ptvOi2nEiVKmKCgICPJXHvttdb4mjVrZpjWW33DGOf18Ep92CGr7V12+kxW68yhQ4dMrVq1rJoCAgJMiRIljCRjt9vNhx9+aCpVqmQkmc8++yzD9MnJyaZfv34Zlmd4eLhTn5dk9u/fn+3XlV76Zf7uu+9a61Z4eLgJCAiwnr9+/frm2LFjGabPTn91yM9984MPPui0X65UqZKpXLmyeeaZZzJ9viutL9ldhhs2bDBFixZ12i4XL17cSDL+/v7m448/zrT23Lz29NM69vMlS5Z02h4EBga63GdmZ1v3+++/m/Lly1vPFRwc7HSsEhQUlOn+ODv9MDuvPTv7littN77++mvr/XD0w1KlSlnbNMdj7NixOXrenHDMY9q0aSYqKspaT9MfTwYFBZn58+e7nN4T24IrSf9+hYaGmsjISOtRunRp4+/v79QHZ8+eneWycLVujBkzxml5VKxY0VSuXNn06NHDGOO8PqR/L9PPOzQ01KxYsSLT2jM7pgoPD3d6TZc/Bg4c6PScuTnWALKLM4mAAuKGG27Qb7/9ptjYWJUrV04XL15UiRIl9MADD2jTpk2qVq2a1bZEiRIZpi9XrpxWrVqlzz77TN26dVPZsmV19uxZpaSkKDo6Wl27dtXEiRO1YsWKPKn3s88+03vvvaeGDRvq4sWLKlq0qFq2bKmPP/5YM2fOzLPbuuY1f39/LVmyRP369VOVKlWUlJSkPXv2aM+ePT5x2u4jjzyiBQsWqE2bNipWrJguXryo8uXL64knntCvv/6q6667Ll/m261bN3388ceKjY1V/fr1FRYWpsTERBUvXlzNmjXTmDFj9Mcff6hWrVpuPX+PHj30xx9/6OGHH1a1atWUnJwsf39/NWjQQKNGjdLWrVtVu3btPH5Vectms2n06NH67bff9Nhjj6l27dry8/NTYmKiwsPD1apVKz333HNas2aNdQZAQfL0009rw4YNuu+++1SxYkWdPXtWRYoUUYsWLfTmm29q/fr1Wd52PTueeuopLVu2TLfccovCw8N1/vx5RUdH68UXX9TPP/9sfQPuahvnrb7hi6KiorR+/XoNGzZMNWvWlN1ul7+/v2655Rb99NNP6t+/vxITEyW5XpaBgYGaMmWK1qxZo759+6patWpKTU3VmTNnVKZMGbVp00bDhw/Xb7/9luPbnLvy+OOP64cfflCnTp1kt9tlt9tVq1YtjR49WmvXrlWpUqVyPY/8MmnSJI0cOdJav/bu3as9e/bk+wWjJalx48Zat26d7rnnHkVERCgtLU3FixfXPffcozVr1uj+++/Pl/mWL19e3377rZ5++mm1aNFCZcuW1ZkzZ+Tv7686dero8ccf19atW62fPubUtddeqz/++EMjR45UgwYN5O/vr+TkZFWrVk2PPPKI/vjjD7ef25O6d++uHTt2aMSIEWrWrJmKFSumU6dOKSgoSPXr11e/fv309ddf67nnnsv3WqpUqaLNmzfr8ccfV+nSpZWSkqIyZcqoZ8+e2rx5s7p06eJyOk9vC67k9OnT1s+KExISdOzYMYWEhKhx48YaNGiQ/vjjD7d+mvrCCy/orbfeUpMmTRQQEKD9+/drz5491g0PmjZtqs8//1yPPvqoGjdurIiICJ0+fVrBwcFq0KCBBg0apD///NOts/FPnjzp9Joufzi21Q6F/VgDvsFmHEdcAAq0KVOm6KGHHlLVqlVdXrfIE/r27auZM2eqT58+md4KFQDc1atXL3366ad64IEHrAuFIue2b9+ua665RtKlUKNixYoer2HGjBmKjY1V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2dwKAVEwrF69Wv/617+uGBBJly4Y3aNHD33xxRceqEy6/vrrs70uValSRfHx8flbUC5ERkZm+nPS/OLrt4uPjo7mp7N5hJDoKlG9enUtWLBAY8aMkb9/1m/7hQsXtGDBAlWvXt1D1V1y/vx5TZ8+XTt27NB1112n+++/X35+fjp48KCeeeYZLVu2TAEBAerSpYvGjh2rUqVKebQ+X1ajRg116tRJDz74oBo0aODtcpxER0crKChIf/75Z7bae/pgn75RuNE33Ddw4EDdf//9atasmYYOHaoOHTqoTJkyTm2OHDmiH3/8UePGjdPevXv10ksveay+I0eO6I033rD6xdNPP62wsDBt3bpV/fv31/r162Wz2dS+fXu9++67Hr3mT07s2LFDtWvXlt1u18WLFz0yz4CAAHXq1En9+vXTrbfe6vZZbfmlUaNGmj17tk6dOuWT1xQr6AGq5N0Q1ddDwOwi4HWNEBXeQgCdh7z7azd4yttvv21sNptp27atWbVqlUlLS8vQJi0tzaxcudK0adPG2O12884773isvrNnz5qGDRtadwlx3EkqKSnJ1K1b19hsNlOyZEnj5+dnbDabufbaa825c+c8Vl9ObNu2zdjtduPn5+exeaa/fkPjxo3N+++/bxITEz02/6zcddddxs/Pz5w5cyZb7T39+2D6hufQN5z5et8wxpixY8eagIAAaxmGhoaaihUrmooVK5rQ0FBreEBAgBk3bpzH6jpx4oR1HRXHnaSaNGlijh49aipUqGCCg4NNw4YNTVRUlLHZbKZixYrm5MmTHqsvJ3bu3GkqV65soqOjPTbP9P0iKirKDB482Pz9998em/+VvP7668Zms5nFixdnq/0zzzzj0eX3ySefGJvNZmrXrm0++eQTk5CQkKFNQkKC+fe//21q1apl7Ha7+fTTTz1Wn2P+gwYNMnfccYcZMWKEdYfM33//3bRo0cL4+fkZf39/06lTJ7Njxw6P1nbvvfcau92e7T7pq9cN2bZtm7HZbB7dp/n7+5tbb73VzJs3z1y8eNFj882JZs2amSpVqph//vnnim0TExNNdHS0adasmQcqy55z586ZlJQUb5dxRRcvXrSu24VLatWqZapUqZLt9r66bfEFhERXibS0NNOvXz/rwLB48eKmXr16pnXr1qZ169amXr16pnjx4tYBd//+/T1a35tvvmlsNpu5//77zbfffmseeughY7fbTe/evU3FihXN+vXrjTHGnDlzxjz44IPGbrebN954w6M1ZpfjoMFms3lsnjabzdxwww3muuuus97jokWLmj59+pjly5d7rA5XXnrpJWOz2bJ9q9M+ffp4dNnRNzyHvuHM1/uGw/bt282QIUNMq1atTOnSpU1gYKAJDAw0pUuXNq1atTJDhgzxeMAwcuRIY7PZzLBhw8xvv/1mLctOnTqZunXrOl1IdsSIEcZms5mRI0d6tEZfZrPZTNeuXU3Xrl2Nv7+/1TdiYmLMxx9/7PWg+ezZsyY+Pj7bAao3+GqAaozvh6i+HgJmFwGva74eovpygJoThJQZFZYA2hcQEl1lfvrpJ9OzZ09Trlw568DB8ShXrpzp2bOnWbp0qcfratKkibnuuusyDLPb7eazzz5zGn7hwgVTsWJFc/3113uyRJ+W/m4Dv/zyi+nfv78JDQ21DiZq1qxpXn31VZc76vx25MgRs2zZMq/MOyfoG4UTfaNwql+/vmnZsqXTMMeZfvPnz8/QvlatWqZJkyaeKs/npe8XBw8eNOPGjTPVqlWz+kWJEiXMY489ZjZu3OjlSn2bLwaoxvh+iFoQQkBf5esBr4Ovhqi+HqDmBCFlRoUlgPYFhERXsaSkJHPw4EFz8OBBk5SU5NVaIiIizBNPPOE07KmnnjJ2u93lB6jY2FhTsmRJT5Xn81zdkjIpKclMnz7d3HDDDdZGPTAw0Nx+++1mwYIFLn9WhUvoG4UHfcO7Jk6cmKNTv7OrRIkS5qmnnnIa9swzzxi73W6OHz+eoX3//v1NWFhYntdRUGV2G+Nly5aZ++67z4SEhFh9o2HDhmbSpEnWt+0FRX6tewUBIWrhVZACXl8MUX09QPV1vh5SEkDnHbu3r4kE7wkJCVHZsmVVtmxZhYSEXLH9zJkz1a5du3ypJSkpKcNF7hwXq7z8QqmSFBUV5dG7/BREISEh6tu3r1auXKlt27bpmWeeUcmSJTVv3jx17dpVlStX9naJ2Zaf654r9I3Cjb7hOadOndKePXvy/HkvXLig4OBgp2GOflKyZMkM7SMiInTu3Lk8ryM7Vq1apXHjxqlv3766/fbbdfvtt6tv374aN26cVq1a5ZWaMhMTE6N///vfOnTokN599101aNBAW7Zs0YABA1S+fHlvl5cj+bXu5ZW33npLVatWzZfn3rNnj5o3b+40rHHjxpKkli1bZmjfunVrbd++PV9qyS/5ufwKirJly+qFF17Qjh07tHTpUt17771KSUnR+++/r6ZNm6pRo0Z67733lJiY6JX6qlevrpdeekmrV6/WkSNHlJycrOTkZB05ckSrV6/WSy+9pBo1ani0pq+//lotWrTQ6NGjdd1112nIkCGKiYnRjz/+qFdeecXpVvIjR45UzZo1NX/+fI/W6OuaNGmib7/9Vnv37tXYsWNVpUoVrVixQn379lXZsmX1+OOPa9OmTV6prUiRIqpcubKKFi3qlfkXJtzdDNkWHx+v5cuX58tzR0RE6MiRI07DihQp4vJgX5KOHz/utTue7N+/X8uXL9f27dutHW9YWJhq1KihG2+8Mdu3s/aka665Rq+++qrGjx+v//znP5o6daoWLlzo7bKyLT/XvbxA37iEvuF5vt438kvp0qV1+PBhp2HlypVTo0aNXLZPSEhQeHi4J0qzrF27Vv3799eff/4pY4zLNjabTXXq1NGUKVPUokULj9aXldDQUD322GN67LHH9Ouvv+qjjz7Sp59+6u2yCpX8DLEKUojqLk+EgKtWrcp0nxYTE6MbbrghX+efEzExMYqJidGkSZP0ySefaOrUqdq8ebMGDBigQYMG6cyZM94uMUfeeustvfXWW9q1a1eePu+ePXvUt29fp2GNGzfWihUrMg1QP//88zytobBwhJQvvPCCli9fro8++khfffWV3n//fU2ePFn169dXv3791KtXL4WFhXm73GzLr3WvoCEkgk+oWbOmtm7d6jRs0KBBGjRokMv2u3bt8vgHzp07d2rAgAH68ccfJSnDQb/NZpMkdezYUW+//bbHb5OeHX5+furevbu6d+/OLR8LCPqGZ9A3Cpa6detqy5YtTsP69++v/v37u2y/fft2j551sHnzZusMrz59+qhjx46qUaOGQkNDJV26Lfr27du1cOFCzZ49W+3atdPatWtVv359j9WYXfXr19c777yj119/3dulIJsKQojqywh4vY+zUAkpvcXXz0L1FEIi+ISWLVtq0qRJSklJUWBgYJZtT548qVWrVunhhx/2UHXS7t271aJFCx0/flxt2rTJ8oB/4cKFatWqlX755RdVqVLFYzXmVNmyZb1dArKBvuF59A3fd/PNN2vatGnZ6hcHDx7Uzz//rGeeecZD1UnD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", "text/plain": [ "
" ] @@ -663,39 +683,49 @@ "source": [ "# Best Fingerprint Method / Performance\n", "from collections import defaultdict\n", + "\n", "res_dict = defaultdict(list)\n", "for i, row in df_training_stats.iterrows():\n", - " fp_name = row['param_fp_transformer'] \n", - " if(\"Morgan\" in str(fp_name)):\n", + " fp_name = row[\"param_fp_transformer\"]\n", + " if \"Morgan\" in str(fp_name):\n", " res_dict[fp_name].append(row)\n", "\n", "for fp_type, rows in res_dict.items():\n", " df = pd.DataFrame(rows)\n", - " df =df.sort_values(by=\"mean_test_score\")\n", - "\n", - " #plot test score vs. approach\n", - " xlabels = map(lambda x: \"_\".join(x), zip(df.param_fp_transformer__nBits.astype(str), df.param_regressor__alpha.astype(str)))\n", - "\n", - " \n", - " plt.figure(figsize=[14,5])\n", + " df = df.sort_values(by=\"mean_test_score\")\n", + "\n", + " # plot test score vs. approach\n", + " xlabels = map(\n", + " lambda x: \"_\".join(x),\n", + " zip(\n", + " df.param_fp_transformer__fpSize.astype(str),\n", + " df.param_regressor__alpha.astype(str),\n", + " ),\n", + " )\n", + "\n", + " plt.figure(figsize=[14, 5])\n", " plt.bar(range(len(df)), df.mean_test_score, yerr=df.std_test_score)\n", " plt.xticks(range(len(df)), xlabels, rotation=90, fontsize=14)\n", " plt.ylabel(\"mean score\", fontsize=14)\n", " plt.xlabel(\"bitsize_alpha\", fontsize=14)\n", "\n", - " plt.title(\"Fingerprint Transformer \"+str(fp_type).split(\"(\")[0]+\" per Bitsize\", fontsize=18)\n", + " plt.title(\n", + " \"Fingerprint Transformer \" + str(fp_type).split(\"(\")[0] + \" per Bitsize\",\n", + " fontsize=18,\n", + " )\n", " pass" ] }, { "cell_type": "code", "execution_count": 8, + "id": "20f7e139", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:09.030301Z", - "iopub.status.busy": "2024-04-12T12:11:09.030024Z", - "iopub.status.idle": "2024-04-12T12:11:09.425867Z", - "shell.execute_reply": "2024-04-12T12:11:09.425174Z" + "iopub.execute_input": "2024-11-24T09:28:28.915675Z", + "iopub.status.busy": "2024-11-24T09:28:28.915430Z", + "iopub.status.idle": "2024-11-24T09:28:29.314170Z", + "shell.execute_reply": "2024-11-24T09:28:29.313570Z" } }, "outputs": [ @@ -711,10 +741,10 @@ } ], "source": [ - "#plot ALL test score vs. approach\n", - "df =df_training_stats.sort_values(by=\"mean_test_score\")\n", + "# plot ALL test score vs. approach\n", + "df = df_training_stats.sort_values(by=\"mean_test_score\")\n", "\n", - "plt.figure(figsize=[16,9])\n", + "plt.figure(figsize=[16, 9])\n", "plt.bar(range(len(df)), df.mean_test_score, yerr=df.std_test_score)\n", "plt.ylabel(\"mean score\", fontsize=14)\n", "plt.xticks(range(len(df))[::5], df.param_fp_transformer[::5], rotation=90, fontsize=14)\n", @@ -724,6 +754,7 @@ }, { "cell_type": "markdown", + "id": "f407671c", "metadata": {}, "source": [ "This file have the following licence:\n", @@ -955,5 +986,5 @@ } }, "nbformat": 4, - "nbformat_minor": 1 + "nbformat_minor": 5 } diff --git a/notebooks/09_Combinatorial_Method_Usage_with_FingerPrint_Transformers.py b/notebooks/09_Combinatorial_Method_Usage_with_FingerPrint_Transformers.py index d8c51bb..4a39a02 100644 --- a/notebooks/09_Combinatorial_Method_Usage_with_FingerPrint_Transformers.py +++ b/notebooks/09_Combinatorial_Method_Usage_with_FingerPrint_Transformers.py @@ -24,7 +24,7 @@ # * Training Phase # * Analysis # -# Authors: @VincentAlexanderScholz, @RiesBen +# Authors: @VincentAlexanderScholz, @RiesBen # # ## Imports: # First we will import all the stuff that we will need for our work. @@ -62,12 +62,13 @@ csv_file = "SLC6A4_active_excape_export.csv" if not os.path.exists(csv_file): import urllib.request + url = "https://ndownloader.figshare.com/files/25747817" urllib.request.urlretrieve(url, csv_file) else: - csv_file = '../tests/data/SLC6A4_active_excapedb_subset.csv' + csv_file = "../tests/data/SLC6A4_active_excapedb_subset.csv" -#Parse Database +# Parse Database data = pd.read_csv(csv_file) PandasTools.AddMoleculeColumnToFrame(data, smilesCol="SMILES") @@ -77,31 +78,42 @@ # ## Build Pipeline: # In this stage we will build the Pipeline consisting of the featurization part (finger print transformers) and the model part (Ridge Regression). # -# Note that the featurization in this section is an hyperparameter, living in `param_grid`, and the `"fp_transformer"` string is just a placeholder, being replaced during pipeline execution. +# Note that the featurization in this section is an hyperparameter, living in `param_grid`, and the `"fp_transformer"` string is just a placeholder, being replaced during pipeline execution. # # This way we can define multiple different scenarios in `param_grid`, that allow us to rapidly explore different combinations of settings and methodologies. # %% regressor = Ridge() -optimization_pipe = Pipeline([("fp_transformer", "fp_transformer"), # this is a placeholder for different transformers - ("regressor", regressor)]) - -param_grid = [ # Here pass different Options and Approaches +optimization_pipe = Pipeline( + [ + ( + "fp_transformer", + "fp_transformer", + ), # this is a placeholder for different transformers + ("regressor", regressor), + ] +) + +param_grid = [ # Here pass different Options and Approaches { - "fp_transformer": [fingerprints.MorganFingerprintTransformer(), - fingerprints.AvalonFingerprintTransformer()], - "fp_transformer__nBits": [2**x for x in range(8,13)], + "fp_transformer": [ + fingerprints.MorganFingerprintTransformer(), + fingerprints.AvalonFingerprintTransformer(), + ], + "fp_transformer__fpSize": [2**x for x in range(8, 13)], }, { - "fp_transformer": [fingerprints.RDKitFingerprintTransformer(), - fingerprints.AtomPairFingerprintTransformer(), - fingerprints.MACCSKeysFingerprintTransformer()], + "fp_transformer": [ + fingerprints.RDKitFingerprintTransformer(), + fingerprints.AtomPairFingerprintTransformer(), + fingerprints.MACCSKeysFingerprintTransformer(), + ], }, ] global_options = { - "regressor__alpha": np.linspace(0.1,1,5), + "regressor__alpha": np.linspace(0.1, 1, 5), } [params.update(global_options) for params in param_grid] @@ -114,18 +126,19 @@ # %% # Split Data -mol_list_train, mol_list_test, y_train, y_test = train_test_split(data.ROMol, data.pXC50, random_state=0) +mol_list_train, mol_list_test, y_train, y_test = train_test_split( + data.ROMol, data.pXC50, random_state=0 +) # Define Search Process -grid = GridSearchCV(optimization_pipe, n_jobs=1, - param_grid=param_grid) +grid = GridSearchCV(optimization_pipe, n_jobs=1, param_grid=param_grid) # Train t0 = time() grid.fit(mol_list_train, y_train.values) t1 = time() -print(f'Runtime: {t1-t0:0.2F}') +print(f"Runtime: {t1-t0:0.2F}") # %% [markdown] # ## Analysis @@ -140,17 +153,20 @@ # Best Fingerprint Method / Performance res_dict = {} for i, row in df_training_stats.iterrows(): - fp_name = row['param_fp_transformer'] - if(fp_name in res_dict and row['mean_test_score'] > res_dict[fp_name]["mean_test_score"]): + fp_name = row["param_fp_transformer"] + if ( + fp_name in res_dict + and row["mean_test_score"] > res_dict[fp_name]["mean_test_score"] + ): res_dict[fp_name] = row.to_dict() - elif(not fp_name in res_dict): + elif not fp_name in res_dict: res_dict[fp_name] = row.to_dict() - + df = pd.DataFrame(list(res_dict.values())) -df =df.sort_values(by="mean_test_score") +df = df.sort_values(by="mean_test_score") -#plot test score vs. approach -plt.figure(figsize=[14,5]) +# plot test score vs. approach +plt.figure(figsize=[14, 5]) plt.bar(range(len(df)), df.mean_test_score, yerr=df.std_test_score) plt.xticks(range(len(df)), df.param_fp_transformer, rotation=90, fontsize=14) plt.ylabel("mean score", fontsize=14) @@ -161,35 +177,44 @@ # %% # Best Fingerprint Method / Performance from collections import defaultdict + res_dict = defaultdict(list) for i, row in df_training_stats.iterrows(): - fp_name = row['param_fp_transformer'] - if("Morgan" in str(fp_name)): + fp_name = row["param_fp_transformer"] + if "Morgan" in str(fp_name): res_dict[fp_name].append(row) for fp_type, rows in res_dict.items(): df = pd.DataFrame(rows) - df =df.sort_values(by="mean_test_score") - - #plot test score vs. approach - xlabels = map(lambda x: "_".join(x), zip(df.param_fp_transformer__nBits.astype(str), df.param_regressor__alpha.astype(str))) - - - plt.figure(figsize=[14,5]) + df = df.sort_values(by="mean_test_score") + + # plot test score vs. approach + xlabels = map( + lambda x: "_".join(x), + zip( + df.param_fp_transformer__fpSize.astype(str), + df.param_regressor__alpha.astype(str), + ), + ) + + plt.figure(figsize=[14, 5]) plt.bar(range(len(df)), df.mean_test_score, yerr=df.std_test_score) plt.xticks(range(len(df)), xlabels, rotation=90, fontsize=14) plt.ylabel("mean score", fontsize=14) plt.xlabel("bitsize_alpha", fontsize=14) - plt.title("Fingerprint Transformer "+str(fp_type).split("(")[0]+" per Bitsize", fontsize=18) + plt.title( + "Fingerprint Transformer " + str(fp_type).split("(")[0] + " per Bitsize", + fontsize=18, + ) pass # %% -#plot ALL test score vs. approach -df =df_training_stats.sort_values(by="mean_test_score") +# plot ALL test score vs. approach +df = df_training_stats.sort_values(by="mean_test_score") -plt.figure(figsize=[16,9]) +plt.figure(figsize=[16, 9]) plt.bar(range(len(df)), df.mean_test_score, yerr=df.std_test_score) plt.ylabel("mean score", fontsize=14) plt.xticks(range(len(df))[::5], df.param_fp_transformer[::5], rotation=90, fontsize=14) diff --git a/notebooks/10_pipeline_pandas_output.ipynb b/notebooks/10_pipeline_pandas_output.ipynb index 09275ca..7fcff28 100644 --- a/notebooks/10_pipeline_pandas_output.ipynb +++ b/notebooks/10_pipeline_pandas_output.ipynb @@ -2,6 +2,7 @@ "cells": [ { "cell_type": "markdown", + "id": "454d87b5", "metadata": {}, "source": [ "# Preserving feature information in DataFrames\n", @@ -14,12 +15,13 @@ { "cell_type": "code", "execution_count": 1, + "id": "cb457069", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:10.927499Z", - "iopub.status.busy": "2024-04-12T12:11:10.927303Z", - "iopub.status.idle": "2024-04-12T12:11:11.787084Z", - "shell.execute_reply": "2024-04-12T12:11:11.786420Z" + "iopub.execute_input": "2024-11-24T09:28:31.171627Z", + "iopub.status.busy": "2024-11-24T09:28:31.171255Z", + "iopub.status.idle": "2024-11-24T09:28:32.152283Z", + "shell.execute_reply": "2024-11-24T09:28:32.151641Z" } }, "outputs": [], @@ -42,12 +44,13 @@ { "cell_type": "code", "execution_count": 2, + "id": "bc72ca09", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:11.789862Z", - "iopub.status.busy": "2024-04-12T12:11:11.789572Z", - "iopub.status.idle": "2024-04-12T12:11:11.796138Z", - "shell.execute_reply": "2024-04-12T12:11:11.795520Z" + "iopub.execute_input": "2024-11-24T09:28:32.155106Z", + "iopub.status.busy": "2024-11-24T09:28:32.154793Z", + "iopub.status.idle": "2024-11-24T09:28:32.161683Z", + "shell.execute_reply": "2024-11-24T09:28:32.161035Z" } }, "outputs": [], @@ -60,6 +63,7 @@ }, { "cell_type": "markdown", + "id": "f482cac3", "metadata": {}, "source": [ "Let's split the dataset in training and test, so we will be able to use the test set to evaluate the performance of models trained on the training set." @@ -68,12 +72,13 @@ { "cell_type": "code", "execution_count": 3, + "id": "6019d09f", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:11.798853Z", - "iopub.status.busy": "2024-04-12T12:11:11.798596Z", - "iopub.status.idle": "2024-04-12T12:11:11.803335Z", - "shell.execute_reply": "2024-04-12T12:11:11.802809Z" + "iopub.execute_input": "2024-11-24T09:28:32.164164Z", + "iopub.status.busy": "2024-11-24T09:28:32.163946Z", + "iopub.status.idle": "2024-11-24T09:28:32.168382Z", + "shell.execute_reply": "2024-11-24T09:28:32.167874Z" } }, "outputs": [], @@ -84,12 +89,13 @@ { "cell_type": "code", "execution_count": 4, + "id": "fe9efa0e", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:11.805768Z", - "iopub.status.busy": "2024-04-12T12:11:11.805514Z", - "iopub.status.idle": "2024-04-12T12:11:11.809489Z", - "shell.execute_reply": "2024-04-12T12:11:11.808992Z" + "iopub.execute_input": "2024-11-24T09:28:32.171037Z", + "iopub.status.busy": "2024-11-24T09:28:32.170722Z", + "iopub.status.idle": "2024-11-24T09:28:32.174941Z", + "shell.execute_reply": "2024-11-24T09:28:32.174393Z" } }, "outputs": [], @@ -105,6 +111,7 @@ }, { "cell_type": "markdown", + "id": "7b4cca39", "metadata": {}, "source": [ "## Descriptors pipeline that returns DataFrames\n", @@ -122,26 +129,432 @@ { "cell_type": "code", "execution_count": 5, + "id": "33ce774b", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:11.811852Z", - "iopub.status.busy": "2024-04-12T12:11:11.811625Z", - "iopub.status.idle": "2024-04-12T12:11:11.827762Z", - "shell.execute_reply": "2024-04-12T12:11:11.827206Z" + "iopub.execute_input": "2024-11-24T09:28:32.177459Z", + "iopub.status.busy": "2024-11-24T09:28:32.177241Z", + "iopub.status.idle": "2024-11-24T09:28:32.194656Z", + "shell.execute_reply": "2024-11-24T09:28:32.194079Z" } }, "outputs": [ { "data": { "text/html": [ - "
Pipeline(steps=[('smilestomoltransformer', SmilesToMolTransformer()),\n",
+       "
Pipeline(steps=[('smilestomoltransformer', SmilesToMolTransformer()),\n",
        "                ('standardizer', Standardizer()),\n",
        "                ('moleculardescriptortransformer',\n",
        "                 MolecularDescriptorTransformer(desc_list=['MaxAbsEStateIndex',\n",
        "                                                           'MaxEStateIndex',\n",
        "                                                           'MinAbsEStateIndex',\n",
        "                                                           'MinEStateIndex',\n",
-       "                                                           'qed', 'MolWt',\n",
+       "                                                           'qed', 'SPS',\n",
+       "                                                           'MolWt',\n",
        "                                                           'HeavyAtomMolWt',\n",
        "                                                           'ExactMolWt',\n",
        "                                                           'NumValenceElectrons',\n",
@@ -162,15 +575,15 @@
        "                                                           'BCUT2D_MRHI',\n",
        "                                                           'BCUT2D_MRLOW',\n",
        "                                                           'AvgIpc', 'BalabanJ',\n",
-       "                                                           'BertzCT', 'Chi0',\n",
-       "                                                           'Chi0n', ...]))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "Pipeline(steps=[('smilestomoltransformer', SmilesToMolTransformer()),\n", @@ -218,7 +631,8 @@ " 'MaxEStateIndex',\n", " 'MinAbsEStateIndex',\n", " 'MinEStateIndex',\n", - " 'qed', 'MolWt',\n", + " 'qed', 'SPS',\n", + " 'MolWt',\n", " 'HeavyAtomMolWt',\n", " 'ExactMolWt',\n", " 'NumValenceElectrons',\n", @@ -239,8 +653,7 @@ " 'BCUT2D_MRHI',\n", " 'BCUT2D_MRLOW',\n", " 'AvgIpc', 'BalabanJ',\n", - " 'BertzCT', 'Chi0',\n", - " 'Chi0n', ...]))])" + " 'BertzCT', 'Chi0', ...]))])" ] }, "execution_count": 5, @@ -260,15 +673,541 @@ { "cell_type": "code", "execution_count": 6, + "id": "2cb55603", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:11.830134Z", - "iopub.status.busy": "2024-04-12T12:11:11.829918Z", - "iopub.status.idle": "2024-04-12T12:11:13.735020Z", - "shell.execute_reply": "2024-04-12T12:11:13.734450Z" + "iopub.execute_input": "2024-11-24T09:28:32.196995Z", + "iopub.status.busy": "2024-11-24T09:28:32.196792Z", + "iopub.status.idle": "2024-11-24T09:28:34.209289Z", + "shell.execute_reply": "2024-11-24T09:28:34.208617Z" } }, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:32] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:33] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:34] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, { "data": { "text/html": [ @@ -295,11 +1234,11 @@ " MinAbsEStateIndex\n", " MinEStateIndex\n", " qed\n", + " SPS\n", " MolWt\n", " HeavyAtomMolWt\n", " ExactMolWt\n", " NumValenceElectrons\n", - " NumRadicalElectrons\n", " ...\n", " fr_sulfide\n", " fr_sulfonamd\n", @@ -321,11 +1260,11 @@ " 0.056985\n", " -0.432587\n", " 0.353101\n", - " 522.592\n", - " 490.336\n", - " 522.233014\n", + " 14.289474\n", + " 522.591980\n", + " 490.335999\n", + " 522.233032\n", " 200.0\n", - " 0.0\n", " ...\n", " 0.0\n", " 0.0\n", @@ -345,11 +1284,11 @@ " 0.026212\n", " -0.050849\n", " 0.682187\n", - " 425.558\n", - " 398.342\n", - " 425.188546\n", + " 16.033333\n", + " 425.558014\n", + " 398.342010\n", + " 425.188538\n", " 158.0\n", - " 0.0\n", " ...\n", " 0.0\n", " 0.0\n", @@ -369,11 +1308,11 @@ " 0.266700\n", " -0.413763\n", " 0.443905\n", - " 465.588\n", - " 432.324\n", - " 465.259169\n", + " 15.852942\n", + " 465.588013\n", + " 432.324005\n", + " 465.259155\n", " 180.0\n", - " 0.0\n", " ...\n", " 0.0\n", " 0.0\n", @@ -388,16 +1327,16 @@ " \n", " \n", " 3\n", - " 12.725824\n", - " 12.725824\n", + " 12.725823\n", + " 12.725823\n", " 0.052996\n", " -0.052996\n", " 0.577709\n", - " 478.468\n", - " 445.204\n", - " 477.206216\n", + " 17.812500\n", + " 478.467987\n", + " 445.204010\n", + " 477.206207\n", " 174.0\n", - " 0.0\n", " ...\n", " 0.0\n", " 0.0\n", @@ -407,7 +1346,7 @@ " 0.0\n", " 0.0\n", " 0.0\n", - " 1.0\n", + " 0.0\n", " 0.0\n", " \n", " \n", @@ -417,11 +1356,11 @@ " 0.898244\n", " 0.898244\n", " 0.658108\n", - " 246.313\n", - " 232.201\n", - " 246.115698\n", + " 13.052631\n", + " 246.313004\n", + " 232.201004\n", + " 246.115692\n", " 92.0\n", - " 0.0\n", " ...\n", " 0.0\n", " 0.0\n", @@ -465,11 +1404,11 @@ " 0.175664\n", " 0.175664\n", " 0.916154\n", - " 312.240\n", - " 293.088\n", - " 311.084370\n", + " 35.700001\n", + " 312.239990\n", + " 293.088013\n", + " 311.084381\n", " 108.0\n", - " 0.0\n", " ...\n", " 0.0\n", " 0.0\n", @@ -489,11 +1428,11 @@ " 0.420312\n", " 0.420312\n", " 0.378112\n", - " 465.645\n", - " 430.365\n", + " 21.714285\n", + " 465.644989\n", + " 430.364990\n", " 465.289246\n", " 180.0\n", - " 0.0\n", " ...\n", " 0.0\n", " 0.0\n", @@ -513,11 +1452,11 @@ " 0.300870\n", " -4.299737\n", " 0.919340\n", - " 328.378\n", - " 305.194\n", - " 328.176248\n", + " 23.565218\n", + " 328.377991\n", + " 305.194000\n", + " 328.176239\n", " 128.0\n", - " 0.0\n", " ...\n", " 0.0\n", " 0.0\n", @@ -537,11 +1476,11 @@ " 0.127623\n", " -0.127623\n", " 0.918995\n", - " 323.223\n", - " 307.095\n", - " 322.063968\n", + " 19.428572\n", + " 323.222992\n", + " 307.095001\n", + " 322.063965\n", " 110.0\n", - " 0.0\n", " ...\n", " 0.0\n", " 0.0\n", @@ -561,11 +1500,11 @@ " 0.086367\n", " 0.086367\n", " 0.911854\n", - " 296.414\n", - " 272.222\n", - " 296.188863\n", + " 17.136364\n", + " 296.414001\n", + " 272.221985\n", + " 296.188873\n", " 116.0\n", - " 0.0\n", " ...\n", " 0.0\n", " 0.0\n", @@ -580,7 +1519,7 @@ " \n", " \n", "\n", - "

159 rows × 209 columns

\n", + "

159 rows × 210 columns

\n", "" ], "text/plain": [ @@ -588,7 +1527,7 @@ "0 13.448610 13.448610 0.056985 -0.432587 \n", "1 12.863074 12.863074 0.026212 -0.050849 \n", "2 13.424788 13.424788 0.266700 -0.413763 \n", - "3 12.725824 12.725824 0.052996 -0.052996 \n", + "3 12.725823 12.725823 0.052996 -0.052996 \n", "4 6.356910 6.356910 0.898244 0.898244 \n", ".. ... ... ... ... \n", "154 6.217065 6.217065 0.175664 0.175664 \n", @@ -597,31 +1536,31 @@ "157 6.238476 6.238476 0.127623 -0.127623 \n", "158 6.371723 6.371723 0.086367 0.086367 \n", "\n", - " qed MolWt HeavyAtomMolWt ExactMolWt NumValenceElectrons \\\n", - "0 0.353101 522.592 490.336 522.233014 200.0 \n", - "1 0.682187 425.558 398.342 425.188546 158.0 \n", - "2 0.443905 465.588 432.324 465.259169 180.0 \n", - "3 0.577709 478.468 445.204 477.206216 174.0 \n", - "4 0.658108 246.313 232.201 246.115698 92.0 \n", - ".. ... ... ... ... ... \n", - "154 0.916154 312.240 293.088 311.084370 108.0 \n", - "155 0.378112 465.645 430.365 465.289246 180.0 \n", - "156 0.919340 328.378 305.194 328.176248 128.0 \n", - "157 0.918995 323.223 307.095 322.063968 110.0 \n", - "158 0.911854 296.414 272.222 296.188863 116.0 \n", - "\n", - " NumRadicalElectrons ... fr_sulfide fr_sulfonamd fr_sulfone \\\n", - "0 0.0 ... 0.0 0.0 0.0 \n", - "1 0.0 ... 0.0 0.0 0.0 \n", - "2 0.0 ... 0.0 0.0 0.0 \n", - "3 0.0 ... 0.0 0.0 0.0 \n", - "4 0.0 ... 0.0 0.0 0.0 \n", + " qed SPS MolWt HeavyAtomMolWt ExactMolWt \\\n", + "0 0.353101 14.289474 522.591980 490.335999 522.233032 \n", + "1 0.682187 16.033333 425.558014 398.342010 425.188538 \n", + "2 0.443905 15.852942 465.588013 432.324005 465.259155 \n", + "3 0.577709 17.812500 478.467987 445.204010 477.206207 \n", + "4 0.658108 13.052631 246.313004 232.201004 246.115692 \n", + ".. ... ... ... ... ... \n", + "154 0.916154 35.700001 312.239990 293.088013 311.084381 \n", + "155 0.378112 21.714285 465.644989 430.364990 465.289246 \n", + "156 0.919340 23.565218 328.377991 305.194000 328.176239 \n", + "157 0.918995 19.428572 323.222992 307.095001 322.063965 \n", + "158 0.911854 17.136364 296.414001 272.221985 296.188873 \n", + "\n", + " NumValenceElectrons ... fr_sulfide fr_sulfonamd fr_sulfone \\\n", + "0 200.0 ... 0.0 0.0 0.0 \n", + "1 158.0 ... 0.0 0.0 0.0 \n", + "2 180.0 ... 0.0 0.0 0.0 \n", + "3 174.0 ... 0.0 0.0 0.0 \n", + "4 92.0 ... 0.0 0.0 0.0 \n", ".. ... ... ... ... ... \n", - "154 0.0 ... 0.0 0.0 0.0 \n", - "155 0.0 ... 0.0 0.0 0.0 \n", - "156 0.0 ... 0.0 0.0 0.0 \n", - "157 0.0 ... 0.0 0.0 0.0 \n", - "158 0.0 ... 0.0 0.0 0.0 \n", + "154 108.0 ... 0.0 0.0 0.0 \n", + "155 180.0 ... 0.0 0.0 0.0 \n", + "156 128.0 ... 0.0 0.0 0.0 \n", + "157 110.0 ... 0.0 0.0 0.0 \n", + "158 116.0 ... 0.0 0.0 0.0 \n", "\n", " fr_term_acetylene fr_tetrazole fr_thiazole fr_thiocyan fr_thiophene \\\n", "0 0.0 0.0 0.0 0.0 0.0 \n", @@ -640,7 +1579,7 @@ "0 0.0 0.0 \n", "1 0.0 0.0 \n", "2 0.0 0.0 \n", - "3 1.0 0.0 \n", + "3 0.0 0.0 \n", "4 0.0 0.0 \n", ".. ... ... \n", "154 0.0 0.0 \n", @@ -649,7 +1588,7 @@ "157 0.0 0.0 \n", "158 0.0 0.0 \n", "\n", - "[159 rows x 209 columns]" + "[159 rows x 210 columns]" ] }, "execution_count": 6, @@ -664,6 +1603,7 @@ }, { "cell_type": "markdown", + "id": "40d6024a", "metadata": {}, "source": [ "All scikit-mol transformers are now compatible with the scikit-learn [set_output API](https://scikit-learn.org/stable/auto_examples/miscellaneous/plot_set_output.html).\n", @@ -675,25 +1615,430 @@ { "cell_type": "code", "execution_count": 7, + "id": "f56c539c", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:13.737598Z", - "iopub.status.busy": "2024-04-12T12:11:13.737383Z", - "iopub.status.idle": "2024-04-12T12:11:13.745000Z", - "shell.execute_reply": "2024-04-12T12:11:13.744500Z" + "iopub.execute_input": "2024-11-24T09:28:34.211959Z", + "iopub.status.busy": "2024-11-24T09:28:34.211717Z", + "iopub.status.idle": "2024-11-24T09:28:34.220439Z", + "shell.execute_reply": "2024-11-24T09:28:34.219746Z" } }, "outputs": [ { "data": { "text/html": [ - "
Pipeline(steps=[('smilestomoltransformer', SmilesToMolTransformer()),\n",
+       "
Pipeline(steps=[('smilestomoltransformer', SmilesToMolTransformer()),\n",
        "                ('standardizer', Standardizer()),\n",
        "                ('morganfingerprinttransformer',\n",
-       "                 MorganFingerprintTransformer())])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
SmilesToMolTransformer()
Standardizer()
MorganFingerprintTransformer()
" ], "text/plain": [ "Pipeline(steps=[('smilestomoltransformer', SmilesToMolTransformer()),\n", @@ -719,12 +2064,13 @@ { "cell_type": "code", "execution_count": 8, + "id": "781d1bc8", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:13.747288Z", - "iopub.status.busy": "2024-04-12T12:11:13.747073Z", - "iopub.status.idle": "2024-04-12T12:11:14.181998Z", - "shell.execute_reply": "2024-04-12T12:11:14.181388Z" + "iopub.execute_input": "2024-11-24T09:28:34.222936Z", + "iopub.status.busy": "2024-11-24T09:28:34.222716Z", + "iopub.status.idle": "2024-11-24T09:28:34.618391Z", + "shell.execute_reply": "2024-11-24T09:28:34.617722Z" } }, "outputs": [ @@ -1123,6 +2469,7 @@ }, { "cell_type": "markdown", + "id": "19a13ca2", "metadata": {}, "source": [ "## Analyze feature importance of regression pipeline\n", @@ -1135,31 +2482,438 @@ { "cell_type": "code", "execution_count": 9, + "id": "4872ecab", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:14.184553Z", - "iopub.status.busy": "2024-04-12T12:11:14.184344Z", - "iopub.status.idle": "2024-04-12T12:11:14.201432Z", - "shell.execute_reply": "2024-04-12T12:11:14.200853Z" + "iopub.execute_input": "2024-11-24T09:28:34.621103Z", + "iopub.status.busy": "2024-11-24T09:28:34.620854Z", + "iopub.status.idle": "2024-11-24T09:28:34.640701Z", + "shell.execute_reply": "2024-11-24T09:28:34.640027Z" } }, "outputs": [ { "data": { "text/html": [ - "
Pipeline(steps=[('smilestomoltransformer', SmilesToMolTransformer()),\n",
+       "
Pipeline(steps=[('smilestomoltransformer', SmilesToMolTransformer()),\n",
        "                ('standardizer', Standardizer()),\n",
        "                ('moleculardescriptortransformer',\n",
        "                 MolecularDescriptorTransformer(desc_list=['MaxAbsEStateIndex',\n",
        "                                                           'MaxEStateIndex',\n",
        "                                                           'MinAbsEStateIndex',\n",
        "                                                           'MinEStateIndex',\n",
-       "                                                           'qed', 'MolWt',\n",
+       "                                                           'qed', 'SPS',\n",
+       "                                                           'MolWt',\n",
        "                                                           'HeavyAtomMolWt',\n",
        "                                                           'ExactMolWt',\n",
        "                                                           'NumValenceElectrons',\n",
        "                                                           'NumRadicalElectrons',\n",
-       "                                                           'MaxPartialC...\n",
+       "                                                           'MaxPa...\n",
+       "                                                           'MaxAbsPartialCharge',\n",
        "                                                           'MinAbsPartialCharge',\n",
        "                                                           'FpDensityMorgan1',\n",
        "                                                           'FpDensityMorgan2',\n",
@@ -1173,22 +2927,23 @@
        "                                                           'BCUT2D_MRHI',\n",
        "                                                           'BCUT2D_MRLOW',\n",
        "                                                           'AvgIpc', 'BalabanJ',\n",
-       "                                                           'BertzCT', 'Chi0',\n",
-       "                                                           'Chi0n', ...])),\n",
+       "                                                           'BertzCT', 'Chi0', ...])),\n",
        "                ('standardscaler', StandardScaler()),\n",
-       "                ('randomforestregressor', RandomForestRegressor(max_depth=5))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
StandardScaler()
RandomForestRegressor(max_depth=5)
" ], "text/plain": [ "Pipeline(steps=[('smilestomoltransformer', SmilesToMolTransformer()),\n", @@ -1231,12 +2986,14 @@ " 'MaxEStateIndex',\n", " 'MinAbsEStateIndex',\n", " 'MinEStateIndex',\n", - " 'qed', 'MolWt',\n", + " 'qed', 'SPS',\n", + " 'MolWt',\n", " 'HeavyAtomMolWt',\n", " 'ExactMolWt',\n", " 'NumValenceElectrons',\n", " 'NumRadicalElectrons',\n", - " 'MaxPartialC...\n", + " 'MaxPa...\n", + " 'MaxAbsPartialCharge',\n", " 'MinAbsPartialCharge',\n", " 'FpDensityMorgan1',\n", " 'FpDensityMorgan2',\n", @@ -1250,8 +3007,7 @@ " 'BCUT2D_MRHI',\n", " 'BCUT2D_MRLOW',\n", " 'AvgIpc', 'BalabanJ',\n", - " 'BertzCT', 'Chi0',\n", - " 'Chi0n', ...])),\n", + " 'BertzCT', 'Chi0', ...])),\n", " ('standardscaler', StandardScaler()),\n", " ('randomforestregressor', RandomForestRegressor(max_depth=5))])" ] @@ -1279,15 +3035,680 @@ { "cell_type": "code", "execution_count": 10, + "id": "f0b2f44f", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:14.203799Z", - "iopub.status.busy": "2024-04-12T12:11:14.203546Z", - "iopub.status.idle": "2024-04-12T12:11:17.092871Z", - "shell.execute_reply": "2024-04-12T12:11:17.092220Z" + "iopub.execute_input": "2024-11-24T09:28:34.643396Z", + "iopub.status.busy": "2024-11-24T09:28:34.643148Z", + "iopub.status.idle": "2024-11-24T09:28:37.656343Z", + "shell.execute_reply": "2024-11-24T09:28:37.655703Z" } }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:35] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:36] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:37] DEPRECATION WARNING: please use MorganGenerator\n" + ] + } + ], "source": [ "regression_pipeline.fit(smis_train, target_train)\n", "pred_test = regression_pipeline.predict(smis_test)" @@ -1295,6 +3716,7 @@ }, { "cell_type": "markdown", + "id": "3aa6802d", "metadata": {}, "source": [ "Let's define a simple function to compute regression metrics, and use it to evaluate the test set performance of the pipeline." @@ -1303,21 +3725,30 @@ { "cell_type": "code", "execution_count": 11, + "id": "8b59851a", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:17.095680Z", - "iopub.status.busy": "2024-04-12T12:11:17.095437Z", - "iopub.status.idle": "2024-04-12T12:11:17.102165Z", - "shell.execute_reply": "2024-04-12T12:11:17.101654Z" + "iopub.execute_input": "2024-11-24T09:28:37.658965Z", + "iopub.status.busy": "2024-11-24T09:28:37.658741Z", + "iopub.status.idle": "2024-11-24T09:28:37.666594Z", + "shell.execute_reply": "2024-11-24T09:28:37.665979Z" } }, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/esben/python_envs/vscode/lib/python3.10/site-packages/sklearn/metrics/_regression.py:492: FutureWarning: 'squared' is deprecated in version 1.4 and will be removed in 1.6. To calculate the root mean squared error, use the function'root_mean_squared_error'.\n", + " warnings.warn(\n" + ] + }, { "data": { "text/plain": [ - "{'RMSE': 0.8750229695931232,\n", - " 'MAE': 0.7227101414064178,\n", - " 'R2': 0.11946989572680278}" + "{'RMSE': 0.8736959928049254,\n", + " 'MAE': 0.707222432887994,\n", + " 'R2': 0.12213852746646214}" ] }, "execution_count": 11, @@ -1341,19 +3772,424 @@ { "cell_type": "code", "execution_count": 12, + "id": "68528957", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:17.104450Z", - "iopub.status.busy": "2024-04-12T12:11:17.104246Z", - "iopub.status.idle": "2024-04-12T12:11:17.108860Z", - "shell.execute_reply": "2024-04-12T12:11:17.108329Z" + "iopub.execute_input": "2024-11-24T09:28:37.668899Z", + "iopub.status.busy": "2024-11-24T09:28:37.668697Z", + "iopub.status.idle": "2024-11-24T09:28:37.673651Z", + "shell.execute_reply": "2024-11-24T09:28:37.672957Z" } }, "outputs": [ { "data": { "text/html": [ - "
RandomForestRegressor(max_depth=5)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + "
RandomForestRegressor(max_depth=5)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" ], "text/plain": [ "RandomForestRegressor(max_depth=5)" @@ -1371,6 +4207,7 @@ }, { "cell_type": "markdown", + "id": "d8f32688", "metadata": {}, "source": [ "Since we used `set_output(transform=\"pandas\")` on the pipeline, the last step of the pipeline (the regression model) has the descriptor names in the `feature_names_in_` attribute. We can use them and the `feature_importances_` attribute to easily analyze the feature importances." @@ -1379,12 +4216,13 @@ { "cell_type": "code", "execution_count": 13, + "id": "24011e90", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:17.111179Z", - "iopub.status.busy": "2024-04-12T12:11:17.110981Z", - "iopub.status.idle": "2024-04-12T12:11:17.124017Z", - "shell.execute_reply": "2024-04-12T12:11:17.123440Z" + "iopub.execute_input": "2024-11-24T09:28:37.677173Z", + "iopub.status.busy": "2024-11-24T09:28:37.676324Z", + "iopub.status.idle": "2024-11-24T09:28:37.690728Z", + "shell.execute_reply": "2024-11-24T09:28:37.690184Z" } }, "outputs": [ @@ -1417,27 +4255,27 @@ " \n", " 0\n", " MaxAbsEStateIndex\n", - " 0.003899\n", + " 0.002776\n", " \n", " \n", " 1\n", " MaxEStateIndex\n", - " 0.001640\n", + " 0.003859\n", " \n", " \n", " 2\n", " MinAbsEStateIndex\n", - " 0.002302\n", + " 0.006311\n", " \n", " \n", " 3\n", " MinEStateIndex\n", - " 0.002898\n", + " 0.004721\n", " \n", " \n", " 4\n", " qed\n", - " 0.008949\n", + " 0.007605\n", " \n", " \n", " ...\n", @@ -1445,50 +4283,50 @@ " ...\n", " \n", " \n", - " 204\n", + " 205\n", " fr_thiazole\n", " 0.000000\n", " \n", " \n", - " 205\n", + " 206\n", " fr_thiocyan\n", " 0.000000\n", " \n", " \n", - " 206\n", + " 207\n", " fr_thiophene\n", - " 0.000286\n", + " 0.000046\n", " \n", " \n", - " 207\n", + " 208\n", " fr_unbrch_alkane\n", - " 0.000020\n", + " 0.000000\n", " \n", " \n", - " 208\n", + " 209\n", " fr_urea\n", - " 0.000015\n", + " 0.000000\n", " \n", " \n", "\n", - "

209 rows × 2 columns

\n", + "

210 rows × 2 columns

\n", "" ], "text/plain": [ " feature importance\n", - "0 MaxAbsEStateIndex 0.003899\n", - "1 MaxEStateIndex 0.001640\n", - "2 MinAbsEStateIndex 0.002302\n", - "3 MinEStateIndex 0.002898\n", - "4 qed 0.008949\n", + "0 MaxAbsEStateIndex 0.002776\n", + "1 MaxEStateIndex 0.003859\n", + "2 MinAbsEStateIndex 0.006311\n", + "3 MinEStateIndex 0.004721\n", + "4 qed 0.007605\n", ".. ... ...\n", - "204 fr_thiazole 0.000000\n", - "205 fr_thiocyan 0.000000\n", - "206 fr_thiophene 0.000286\n", - "207 fr_unbrch_alkane 0.000020\n", - "208 fr_urea 0.000015\n", + "205 fr_thiazole 0.000000\n", + "206 fr_thiocyan 0.000000\n", + "207 fr_thiophene 0.000046\n", + "208 fr_unbrch_alkane 0.000000\n", + "209 fr_urea 0.000000\n", "\n", - "[209 rows x 2 columns]" + "[210 rows x 2 columns]" ] }, "execution_count": 13, @@ -1503,6 +4341,7 @@ }, { "cell_type": "markdown", + "id": "64ac369d", "metadata": {}, "source": [ "Sort the features by most to least important:" @@ -1511,12 +4350,13 @@ { "cell_type": "code", "execution_count": 14, + "id": "713d24f1", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:17.126745Z", - "iopub.status.busy": "2024-04-12T12:11:17.126312Z", - "iopub.status.idle": "2024-04-12T12:11:17.134339Z", - "shell.execute_reply": "2024-04-12T12:11:17.133744Z" + "iopub.execute_input": "2024-11-24T09:28:37.693255Z", + "iopub.status.busy": "2024-11-24T09:28:37.693046Z", + "iopub.status.idle": "2024-11-24T09:28:37.700756Z", + "shell.execute_reply": "2024-11-24T09:28:37.700214Z" } }, "outputs": [ @@ -1549,27 +4389,27 @@ " \n", " 0\n", " PEOE_VSA6\n", - " 0.145391\n", + " 0.147449\n", " \n", " \n", " 1\n", " VSA_EState5\n", - " 0.087551\n", + " 0.087963\n", " \n", " \n", " 2\n", " MaxAbsPartialCharge\n", - " 0.050707\n", + " 0.057491\n", " \n", " \n", " 3\n", " VSA_EState6\n", - " 0.032544\n", + " 0.034922\n", " \n", " \n", " 4\n", " SlogP_VSA6\n", - " 0.030168\n", + " 0.028875\n", " \n", " \n", " ...\n", @@ -1577,50 +4417,50 @@ " ...\n", " \n", " \n", - " 204\n", - " fr_isocyan\n", - " 0.000000\n", - " \n", - " \n", " 205\n", - " fr_isothiocyan\n", + " fr_hdrzine\n", " 0.000000\n", " \n", " \n", " 206\n", - " fr_ketone\n", + " fr_hdrzone\n", " 0.000000\n", " \n", " \n", " 207\n", - " fr_ketone_Topliss\n", + " fr_imidazole\n", " 0.000000\n", " \n", " \n", " 208\n", - " fr_C_S\n", + " fr_imide\n", + " 0.000000\n", + " \n", + " \n", + " 209\n", + " fr_urea\n", " 0.000000\n", " \n", " \n", "\n", - "

209 rows × 2 columns

\n", + "

210 rows × 2 columns

\n", "" ], "text/plain": [ " feature importance\n", - "0 PEOE_VSA6 0.145391\n", - "1 VSA_EState5 0.087551\n", - "2 MaxAbsPartialCharge 0.050707\n", - "3 VSA_EState6 0.032544\n", - "4 SlogP_VSA6 0.030168\n", + "0 PEOE_VSA6 0.147449\n", + "1 VSA_EState5 0.087963\n", + "2 MaxAbsPartialCharge 0.057491\n", + "3 VSA_EState6 0.034922\n", + "4 SlogP_VSA6 0.028875\n", ".. ... ...\n", - "204 fr_isocyan 0.000000\n", - "205 fr_isothiocyan 0.000000\n", - "206 fr_ketone 0.000000\n", - "207 fr_ketone_Topliss 0.000000\n", - "208 fr_C_S 0.000000\n", + "205 fr_hdrzine 0.000000\n", + "206 fr_hdrzone 0.000000\n", + "207 fr_imidazole 0.000000\n", + "208 fr_imide 0.000000\n", + "209 fr_urea 0.000000\n", "\n", - "[209 rows x 2 columns]" + "[210 rows x 2 columns]" ] }, "execution_count": 14, @@ -1636,12 +4476,13 @@ { "cell_type": "code", "execution_count": 15, + "id": "4b97778f", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:17.136673Z", - "iopub.status.busy": "2024-04-12T12:11:17.136477Z", - "iopub.status.idle": "2024-04-12T12:11:17.140656Z", - "shell.execute_reply": "2024-04-12T12:11:17.140049Z" + "iopub.execute_input": "2024-11-24T09:28:37.703238Z", + "iopub.status.busy": "2024-11-24T09:28:37.703004Z", + "iopub.status.idle": "2024-11-24T09:28:37.707129Z", + "shell.execute_reply": "2024-11-24T09:28:37.706557Z" } }, "outputs": [ @@ -1668,6 +4509,7 @@ }, { "cell_type": "markdown", + "id": "f79c93a0", "metadata": {}, "source": [ "## Including external features\n", @@ -1686,12 +4528,13 @@ { "cell_type": "code", "execution_count": 16, + "id": "bf8ddaf9", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:17.143445Z", - "iopub.status.busy": "2024-04-12T12:11:17.142907Z", - "iopub.status.idle": "2024-04-12T12:11:17.189933Z", - "shell.execute_reply": "2024-04-12T12:11:17.189323Z" + "iopub.execute_input": "2024-11-24T09:28:37.709630Z", + "iopub.status.busy": "2024-11-24T09:28:37.709389Z", + "iopub.status.idle": "2024-11-24T09:28:37.755699Z", + "shell.execute_reply": "2024-11-24T09:28:37.755108Z" } }, "outputs": [ @@ -2079,6 +4922,7 @@ }, { "cell_type": "markdown", + "id": "92dc6bf5", "metadata": {}, "source": [ "The CDDD features are stored in columns `cddd_1`, `cddd_2`, ..., `cddd_512`. The file has the identifier column `Ambit_InchiKey` that we can use to combine the CDDD features with the rest of the data:" @@ -2087,12 +4931,13 @@ { "cell_type": "code", "execution_count": 17, + "id": "db83be01", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:17.192512Z", - "iopub.status.busy": "2024-04-12T12:11:17.192295Z", - "iopub.status.idle": "2024-04-12T12:11:17.203133Z", - "shell.execute_reply": "2024-04-12T12:11:17.202544Z" + "iopub.execute_input": "2024-11-24T09:28:37.758200Z", + "iopub.status.busy": "2024-11-24T09:28:37.757994Z", + "iopub.status.idle": "2024-11-24T09:28:37.769048Z", + "shell.execute_reply": "2024-11-24T09:28:37.768408Z" } }, "outputs": [], @@ -2114,12 +4959,13 @@ { "cell_type": "code", "execution_count": 18, + "id": "dae995b7", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:17.205740Z", - "iopub.status.busy": "2024-04-12T12:11:17.205517Z", - "iopub.status.idle": "2024-04-12T12:11:17.209117Z", - "shell.execute_reply": "2024-04-12T12:11:17.208573Z" + "iopub.execute_input": "2024-11-24T09:28:37.771772Z", + "iopub.status.busy": "2024-11-24T09:28:37.771516Z", + "iopub.status.idle": "2024-11-24T09:28:37.775128Z", + "shell.execute_reply": "2024-11-24T09:28:37.774567Z" } }, "outputs": [], @@ -2134,6 +4980,7 @@ }, { "cell_type": "markdown", + "id": "2ec82fc8", "metadata": {}, "source": [ "Now we can define a pipeline that uses the original SMILES column to compute the descriptors available in scikit-mol, then concatenates them with the pre-computed CDDD features, and uses all of them to train the regression model. We will need a slightly more complex pipeline with column selectors and transformers. For more details on this technique, please refer to the [official documentation](https://scikit-learn.org/stable/modules/generated/sklearn.compose.make_column_selector.html).\n", @@ -2144,19 +4991,424 @@ { "cell_type": "code", "execution_count": 19, + "id": "dc6de049", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:17.211432Z", - "iopub.status.busy": "2024-04-12T12:11:17.211209Z", - "iopub.status.idle": "2024-04-12T12:11:17.241254Z", - "shell.execute_reply": "2024-04-12T12:11:17.240639Z" + "iopub.execute_input": "2024-11-24T09:28:37.777567Z", + "iopub.status.busy": "2024-11-24T09:28:37.777354Z", + "iopub.status.idle": "2024-11-24T09:28:37.808615Z", + "shell.execute_reply": "2024-11-24T09:28:37.808024Z" } }, "outputs": [ { "data": { "text/html": [ - "
ColumnTransformer(transformers=[('pipeline-1',\n",
+       "
ColumnTransformer(transformers=[('pipeline-1',\n",
        "                                 Pipeline(steps=[('smilestomoltransformer',\n",
        "                                                  SmilesToMolTransformer()),\n",
        "                                                 ('standardizer',\n",
@@ -2167,10 +5419,11 @@
        "                                                                                            'MinAbsEStateIndex',\n",
        "                                                                                            'MinEStateIndex',\n",
        "                                                                                            'qed',\n",
+       "                                                                                            'SPS',\n",
        "                                                                                            'MolWt',\n",
        "                                                                                            'HeavyAtomMolWt',\n",
        "                                                                                            'ExactMolWt',\n",
-       "                                                                                            'NumValenc...\n",
+       "                                                                                            'Num...\n",
        "                                                                                            'BCUT2D_LOGPHI',\n",
        "                                                                                            'BCUT2D_LOGPLOW',\n",
        "                                                                                            'BCUT2D_MRHI',\n",
@@ -2178,13 +5431,12 @@
        "                                                                                            'AvgIpc',\n",
        "                                                                                            'BalabanJ',\n",
        "                                                                                            'BertzCT',\n",
-       "                                                                                            'Chi0',\n",
-       "                                                                                            'Chi0n', ...]))]),\n",
-       "                                 <sklearn.compose._column_transformer.make_column_selector object at 0x7d90d7ff6e30>),\n",
+       "                                                                                            'Chi0', ...]))]),\n",
+       "                                 <sklearn.compose._column_transformer.make_column_selector object at 0x729f1412c520>),\n",
        "                                ('pipeline-2',\n",
        "                                 Pipeline(steps=[('functiontransformer',\n",
        "                                                  FunctionTransformer())]),\n",
-       "                                 <sklearn.compose._column_transformer.make_column_selector object at 0x7d90d7ff6a40>)])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
<sklearn.compose._column_transformer.make_column_selector object at 0x729f1412ebf0>
FunctionTransformer()
" ], "text/plain": [ "ColumnTransformer(transformers=[('pipeline-1',\n", @@ -2242,10 +5495,11 @@ " 'MinAbsEStateIndex',\n", " 'MinEStateIndex',\n", " 'qed',\n", + " 'SPS',\n", " 'MolWt',\n", " 'HeavyAtomMolWt',\n", " 'ExactMolWt',\n", - " 'NumValenc...\n", + " 'Num...\n", " 'BCUT2D_LOGPHI',\n", " 'BCUT2D_LOGPLOW',\n", " 'BCUT2D_MRHI',\n", @@ -2253,13 +5507,12 @@ " 'AvgIpc',\n", " 'BalabanJ',\n", " 'BertzCT',\n", - " 'Chi0',\n", - " 'Chi0n', ...]))]),\n", - " ),\n", + " 'Chi0', ...]))]),\n", + " ),\n", " ('pipeline-2',\n", " Pipeline(steps=[('functiontransformer',\n", " FunctionTransformer())]),\n", - " )])" + " )])" ] }, "execution_count": 19, @@ -2290,27 +5543,424 @@ { "cell_type": "code", "execution_count": 20, + "id": "6ee85c3c", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:17.243604Z", - "iopub.status.busy": "2024-04-12T12:11:17.243400Z", - "iopub.status.idle": "2024-04-12T12:11:17.309327Z", - "shell.execute_reply": "2024-04-12T12:11:17.308716Z" + "iopub.execute_input": "2024-11-24T09:28:37.811016Z", + "iopub.status.busy": "2024-11-24T09:28:37.810811Z", + "iopub.status.idle": "2024-11-24T09:28:37.883600Z", + "shell.execute_reply": "2024-11-24T09:28:37.882894Z" } }, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/home/esben/python_envs/vscode/lib/python3.10/site-packages/sklearn/preprocessing/_function_transformer.py:345: UserWarning: With transform=\"pandas\", `func` should return a DataFrame to follow the set_output API.\n", - " warnings.warn(\n" - ] - }, { "data": { "text/html": [ - "
Pipeline(steps=[('columntransformer',\n",
+       "
Pipeline(steps=[('columntransformer',\n",
        "                 ColumnTransformer(transformers=[('pipeline-1',\n",
        "                                                  Pipeline(steps=[('smilestomoltransformer',\n",
        "                                                                   SmilesToMolTransformer()),\n",
@@ -2322,16 +5972,16 @@
        "                                                                                                             'MinAbsEStateIndex',\n",
        "                                                                                                             'MinEStateIndex',\n",
        "                                                                                                             'qed',\n",
-       "                                                                                                             'MolWt',\n",
-       "                                                                                                             'He...\n",
-       "                                                                                                             'Chi0n', ...]))]),\n",
-       "                                                  <sklearn.compose._column_transformer.make_column_selector object at 0x7d90d7ff6e30>),\n",
+       "                                                                                                             'SPS',\n",
+       "                                                                                                             'MolW...\n",
+       "                                                                                                             'Chi0', ...]))]),\n",
+       "                                                  <sklearn.compose._column_transformer.make_column_selector object at 0x729f1412c520>),\n",
        "                                                 ('pipeline-2',\n",
        "                                                  Pipeline(steps=[('functiontransformer',\n",
        "                                                                   FunctionTransformer())]),\n",
-       "                                                  <sklearn.compose._column_transformer.make_column_selector object at 0x7d90d7ff6a40>)])),\n",
+       "                                                  <sklearn.compose._column_transformer.make_column_selector object at 0x729f1412ebf0>)])),\n",
        "                ('standardscaler', StandardScaler()),\n",
-       "                ('randomforestregressor', RandomForestRegressor(max_depth=5))])
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
<sklearn.compose._column_transformer.make_column_selector object at 0x729f1412ebf0>
FunctionTransformer()
StandardScaler()
RandomForestRegressor(max_depth=5)
" ], "text/plain": [ "Pipeline(steps=[('columntransformer',\n", @@ -2411,14 +6062,14 @@ " 'MinAbsEStateIndex',\n", " 'MinEStateIndex',\n", " 'qed',\n", - " 'MolWt',\n", - " 'He...\n", - " 'Chi0n', ...]))]),\n", - " ),\n", + " 'SPS',\n", + " 'MolW...\n", + " 'Chi0', ...]))]),\n", + " ),\n", " ('pipeline-2',\n", " Pipeline(steps=[('functiontransformer',\n", " FunctionTransformer())]),\n", - " )])),\n", + " )])),\n", " ('standardscaler', StandardScaler()),\n", " ('randomforestregressor', RandomForestRegressor(max_depth=5))])" ] @@ -2440,21 +6091,672 @@ { "cell_type": "code", "execution_count": 21, + "id": "03960958", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:17.311956Z", - "iopub.status.busy": "2024-04-12T12:11:17.311703Z", - "iopub.status.idle": "2024-04-12T12:11:22.426687Z", - "shell.execute_reply": "2024-04-12T12:11:22.426127Z" + "iopub.execute_input": "2024-11-24T09:28:37.886041Z", + "iopub.status.busy": "2024-11-24T09:28:37.885822Z", + "iopub.status.idle": "2024-11-24T09:28:42.859220Z", + "shell.execute_reply": "2024-11-24T09:28:42.858489Z" } }, "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:38] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:39] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "[10:28:42] DEPRECATION WARNING: please use MorganGenerator\n", + "/home/esben/python_envs/vscode/lib/python3.10/site-packages/sklearn/metrics/_regression.py:492: FutureWarning: 'squared' is deprecated in version 1.4 and will be removed in 1.6. To calculate the root mean squared error, use the function'root_mean_squared_error'.\n", + " warnings.warn(\n" + ] + }, { "data": { "text/plain": [ - "{'RMSE': 0.8103900599888356,\n", - " 'MAE': 0.686626458034167,\n", - " 'R2': 0.2498289359739927}" + "{'RMSE': 0.8314055216871027,\n", + " 'MAE': 0.7061918187521163,\n", + " 'R2': 0.2104167870060334}" ] }, "execution_count": 21, @@ -2471,6 +6773,7 @@ }, { "cell_type": "markdown", + "id": "c49ecd90", "metadata": {}, "source": [ "Let's combine the performance metrics obtained using only the scikit-mol descriptors as input features, and the performance metrics obtained using also the CDDD features:" @@ -2479,12 +6782,13 @@ { "cell_type": "code", "execution_count": 22, + "id": "6ce2fe53", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:22.429148Z", - "iopub.status.busy": "2024-04-12T12:11:22.428932Z", - "iopub.status.idle": "2024-04-12T12:11:22.436592Z", - "shell.execute_reply": "2024-04-12T12:11:22.435962Z" + "iopub.execute_input": "2024-11-24T09:28:42.861769Z", + "iopub.status.busy": "2024-11-24T09:28:42.861505Z", + "iopub.status.idle": "2024-11-24T09:28:42.869169Z", + "shell.execute_reply": "2024-11-24T09:28:42.868553Z" } }, "outputs": [ @@ -2517,15 +6821,15 @@ " \n", " \n", " descriptors\n", - " 0.875023\n", - " 0.722710\n", - " 0.119470\n", + " 0.873696\n", + " 0.707222\n", + " 0.122139\n", " \n", " \n", " combined\n", - " 0.810390\n", - " 0.686626\n", - " 0.249829\n", + " 0.831406\n", + " 0.706192\n", + " 0.210417\n", " \n", " \n", "\n", @@ -2533,8 +6837,8 @@ ], "text/plain": [ " RMSE MAE R2\n", - "descriptors 0.875023 0.722710 0.119470\n", - "combined 0.810390 0.686626 0.249829" + "descriptors 0.873696 0.707222 0.122139\n", + "combined 0.831406 0.706192 0.210417" ] }, "execution_count": 22, @@ -2549,6 +6853,7 @@ }, { "cell_type": "markdown", + "id": "83b7fd13", "metadata": {}, "source": [ "All performance metrics were improved by the includion of the CDDD features.\n", @@ -2558,12 +6863,13 @@ { "cell_type": "code", "execution_count": 23, + "id": "9c98ac71", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:22.439263Z", - "iopub.status.busy": "2024-04-12T12:11:22.438951Z", - "iopub.status.idle": "2024-04-12T12:11:22.453688Z", - "shell.execute_reply": "2024-04-12T12:11:22.453096Z" + "iopub.execute_input": "2024-11-24T09:28:42.872116Z", + "iopub.status.busy": "2024-11-24T09:28:42.871685Z", + "iopub.status.idle": "2024-11-24T09:28:42.886733Z", + "shell.execute_reply": "2024-11-24T09:28:42.886003Z" } }, "outputs": [ @@ -2595,28 +6901,28 @@ " \n", " \n", " 0\n", - " pipeline-2__cddd_102\n", - " 0.077721\n", + " pipeline-1__PEOE_VSA6\n", + " 0.078597\n", " \n", " \n", " 1\n", - " pipeline-1__PEOE_VSA6\n", - " 0.060011\n", + " pipeline-2__cddd_102\n", + " 0.064366\n", " \n", " \n", " 2\n", " pipeline-2__cddd_378\n", - " 0.042489\n", + " 0.045695\n", " \n", " \n", " 3\n", - " pipeline-2__cddd_369\n", - " 0.030706\n", + " pipeline-1__VSA_EState5\n", + " 0.032759\n", " \n", " \n", " 4\n", - " pipeline-1__VSA_EState5\n", - " 0.026225\n", + " pipeline-2__cddd_369\n", + " 0.030738\n", " \n", " \n", " ...\n", @@ -2624,50 +6930,50 @@ " ...\n", " \n", " \n", - " 716\n", - " pipeline-1__SMR_VSA5\n", - " 0.000000\n", - " \n", - " \n", " 717\n", - " pipeline-1__SMR_VSA8\n", + " pipeline-1__fr_lactam\n", " 0.000000\n", " \n", " \n", " 718\n", - " pipeline-1__RingCount\n", + " pipeline-1__fr_NH2\n", " 0.000000\n", " \n", " \n", " 719\n", - " pipeline-1__fr_isocyan\n", + " pipeline-1__SMR_VSA2\n", " 0.000000\n", " \n", " \n", " 720\n", - " pipeline-1__fr_azide\n", + " pipeline-1__fr_Imine\n", + " 0.000000\n", + " \n", + " \n", + " 721\n", + " pipeline-1__fr_phos_acid\n", " 0.000000\n", " \n", " \n", "\n", - "

721 rows × 2 columns

\n", + "

722 rows × 2 columns

\n", "" ], "text/plain": [ - " feature importance\n", - "0 pipeline-2__cddd_102 0.077721\n", - "1 pipeline-1__PEOE_VSA6 0.060011\n", - "2 pipeline-2__cddd_378 0.042489\n", - "3 pipeline-2__cddd_369 0.030706\n", - "4 pipeline-1__VSA_EState5 0.026225\n", - ".. ... ...\n", - "716 pipeline-1__SMR_VSA5 0.000000\n", - "717 pipeline-1__SMR_VSA8 0.000000\n", - "718 pipeline-1__RingCount 0.000000\n", - "719 pipeline-1__fr_isocyan 0.000000\n", - "720 pipeline-1__fr_azide 0.000000\n", - "\n", - "[721 rows x 2 columns]" + " feature importance\n", + "0 pipeline-1__PEOE_VSA6 0.078597\n", + "1 pipeline-2__cddd_102 0.064366\n", + "2 pipeline-2__cddd_378 0.045695\n", + "3 pipeline-1__VSA_EState5 0.032759\n", + "4 pipeline-2__cddd_369 0.030738\n", + ".. ... ...\n", + "717 pipeline-1__fr_lactam 0.000000\n", + "718 pipeline-1__fr_NH2 0.000000\n", + "719 pipeline-1__SMR_VSA2 0.000000\n", + "720 pipeline-1__fr_Imine 0.000000\n", + "721 pipeline-1__fr_phos_acid 0.000000\n", + "\n", + "[722 rows x 2 columns]" ] }, "execution_count": 23, @@ -2684,12 +6990,13 @@ { "cell_type": "code", "execution_count": 24, + "id": "9dbd2a9e", "metadata": { "execution": { - "iopub.execute_input": "2024-04-12T12:11:22.456280Z", - "iopub.status.busy": "2024-04-12T12:11:22.456047Z", - "iopub.status.idle": "2024-04-12T12:11:22.460105Z", - "shell.execute_reply": "2024-04-12T12:11:22.459602Z" + "iopub.execute_input": "2024-11-24T09:28:42.889250Z", + "iopub.status.busy": "2024-11-24T09:28:42.889020Z", + "iopub.status.idle": "2024-11-24T09:28:42.893106Z", + "shell.execute_reply": "2024-11-24T09:28:42.892486Z" } }, "outputs": [ @@ -2698,11 +7005,11 @@ "output_type": "stream", "text": [ "The 5 most important features are:\n", - "pipeline-2__cddd_102\n", "pipeline-1__PEOE_VSA6\n", + "pipeline-2__cddd_102\n", "pipeline-2__cddd_378\n", - "pipeline-2__cddd_369\n", - "pipeline-1__VSA_EState5\n" + "pipeline-1__VSA_EState5\n", + "pipeline-2__cddd_369\n" ] } ], @@ -2715,6 +7022,7 @@ }, { "cell_type": "markdown", + "id": "7b394662", "metadata": {}, "source": [ "As we can see, some CDDD features are among the most important features for the regression model.\n", @@ -2746,5 +7054,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 5 } diff --git a/notebooks/11_safe_inference.ipynb b/notebooks/11_safe_inference.ipynb index 6ee786e..93859ae 100644 --- a/notebooks/11_safe_inference.ipynb +++ b/notebooks/11_safe_inference.ipynb @@ -2,6 +2,7 @@ "cells": [ { "cell_type": "markdown", + "id": "f34dacf0", "metadata": {}, "source": [ "# Safe inference mode\n", @@ -15,22 +16,30 @@ }, { "cell_type": "code", - "execution_count": 12, - "metadata": {}, + "execution_count": 1, + "id": "ac780f4c", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:28:44.417205Z", + "iopub.status.busy": "2024-11-24T09:28:44.417002Z", + "iopub.status.idle": "2024-11-24T09:28:45.205864Z", + "shell.execute_reply": "2024-11-24T09:28:45.205244Z" + } + }, "outputs": [ { "data": { "text/plain": [ - "array([[],\n", - " [],\n", - " [],\n", - " [],\n", + "array([[],\n", + " [],\n", + " [],\n", + " [],\n", " [InvalidMol('SmilesToMolTransformer(safe_inference_mode=True)', error='Invalid Molecule: Explicit valence for atom # 0 N, 4, is greater than permitted')],\n", " [InvalidMol('SmilesToMolTransformer(safe_inference_mode=True)', error='Invalid SMILES: I'm not a SMILES')]],\n", " dtype=object)" ] }, - "execution_count": 12, + "execution_count": 1, "metadata": {}, "output_type": "execute_result" } @@ -39,9 +48,10 @@ "from rdkit import Chem\n", "from scikit_mol.conversions import SmilesToMolTransformer\n", "\n", - "#We have some deprecation warnings, we are adressing them, but they just distract from this demonstration\n", + "# We have some deprecation warnings, we are adressing them, but they just distract from this demonstration\n", "import warnings\n", - "warnings.filterwarnings(\"ignore\", category=DeprecationWarning) \n", + "\n", + "warnings.filterwarnings(\"ignore\", category=DeprecationWarning)\n", "\n", "smiles = [\"C1=CC=C(C=C1)F\", \"C1=CC=C(C=C1)O\", \"C1=CC=C(C=C1)N\", \"C1=CC=C(C=C1)Cl\"]\n", "smiles_with_invalid = smiles + [\"N(C)(C)(C)C\", \"I'm not a SMILES\"]\n", @@ -54,6 +64,7 @@ }, { "cell_type": "markdown", + "id": "bdd18682", "metadata": {}, "source": [ "Without the safe inference mode, the transformation would simply fail, but now we get the expected array back with our RDKit molecules and a last entry which is an object of the type InvalidMol. InvalidMol is simply a placeholder that tells what step failed the conversion and the error. InvalidMol evaluates to `False` in boolean contexts, so it gets easy to filter away and handle in `if`s and list comprehensions. As example:" @@ -61,19 +72,27 @@ }, { "cell_type": "code", - "execution_count": 13, - "metadata": {}, + "execution_count": 2, + "id": "44a6019c", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:28:45.208884Z", + "iopub.status.busy": "2024-11-24T09:28:45.208436Z", + "iopub.status.idle": "2024-11-24T09:28:45.213259Z", + "shell.execute_reply": "2024-11-24T09:28:45.212730Z" + } + }, "outputs": [ { "data": { "text/plain": [ - "[array([], dtype=object),\n", - " array([], dtype=object),\n", - " array([], dtype=object),\n", - " array([], dtype=object)]" + "[array([], dtype=object),\n", + " array([], dtype=object),\n", + " array([], dtype=object),\n", + " array([], dtype=object)]" ] }, - "execution_count": 13, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -84,6 +103,7 @@ }, { "cell_type": "markdown", + "id": "176a44de", "metadata": {}, "source": [ "or" @@ -91,19 +111,27 @@ }, { "cell_type": "code", - "execution_count": 14, - "metadata": {}, + "execution_count": 3, + "id": "8286fd44", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:28:45.215847Z", + "iopub.status.busy": "2024-11-24T09:28:45.215431Z", + "iopub.status.idle": "2024-11-24T09:28:45.219372Z", + "shell.execute_reply": "2024-11-24T09:28:45.218875Z" + } + }, "outputs": [ { "data": { "text/plain": [ - "array([,\n", - " ,\n", - " ,\n", - " ], dtype=object)" + "array([,\n", + " ,\n", + " ,\n", + " ], dtype=object)" ] }, - "execution_count": 14, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -115,6 +143,7 @@ }, { "cell_type": "markdown", + "id": "c7be8909", "metadata": {}, "source": [ "Having a failsafe SmilesToMol conversion leads us to next step, featurization. The transformers in safe inference mode now return a NumPy masked array instead of a regular NumPy array. It simply evaluates the incoming mols in a boolean context, so e.g. `None`, `np.nan` and other Python objects that evaluates to False will also get masked (i.e. if you use a dataframe with an ROMol column produced with the PandasTools utility)" @@ -122,31 +151,30 @@ }, { "cell_type": "code", - "execution_count": 15, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n" - ] + "execution_count": 4, + "id": "9a705642", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:28:45.221712Z", + "iopub.status.busy": "2024-11-24T09:28:45.221465Z", + "iopub.status.idle": "2024-11-24T09:28:45.246566Z", + "shell.execute_reply": "2024-11-24T09:28:45.245960Z" }, + "lines_to_next_cell": 2 + }, + "outputs": [ { "data": { "text/plain": [ "masked_array(\n", - " data=[[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 1, 1,\n", - " 0, 1, 1, 0],\n", - " [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1,\n", - " 0, 0, 1, 0],\n", - " [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 1,\n", - " 0, 0, 0, 0],\n", - " [1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1,\n", - " 0, 1, 0, 1],\n", + " data=[[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0,\n", + " 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0],\n", + " [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0,\n", + " 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 0.0],\n", + " [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0,\n", + " 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0],\n", + " [1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0,\n", + " 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0],\n", " [--, --, --, --, --, --, --, --, --, --, --, --, --, --, --, --,\n", " --, --, --, --, --, --, --, --, --],\n", " [--, --, --, --, --, --, --, --, --, --, --, --, --, --, --, --,\n", @@ -169,11 +197,10 @@ " [ True, True, True, True, True, True, True, True, True,\n", " True, True, True, True, True, True, True, True, True,\n", " True, True, True, True, True, True, True]],\n", - " fill_value=999999,\n", - " dtype=int8)" + " fill_value=1e+20)" ] }, - "execution_count": 15, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -181,13 +208,14 @@ "source": [ "from scikit_mol.fingerprints import MorganFingerprintTransformer\n", "\n", - "mfp = MorganFingerprintTransformer(radius=2, nBits=25, safe_inference_mode=True)\n", + "mfp = MorganFingerprintTransformer(radius=2, fpSize=25, safe_inference_mode=True)\n", "fps = mfp.transform(mols_with_invalid)\n", - "fps\n" + "fps" ] }, { "cell_type": "markdown", + "id": "a5e2b301", "metadata": {}, "source": [ "However, currently scikit-learn models accepts masked arrays, but they do not respect the mask! So if you fed it directly to the model to train, it would seemingly work, but the invalid samples would all have the fill_value, meaning you could get weird results. Instead we need the last part of the puzzle, the SafeInferenceWrapper class." @@ -195,8 +223,17 @@ }, { "cell_type": "code", - "execution_count": 16, - "metadata": {}, + "execution_count": 5, + "id": "37987dc9", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:28:45.249048Z", + "iopub.status.busy": "2024-11-24T09:28:45.248844Z", + "iopub.status.idle": "2024-11-24T09:28:45.318911Z", + "shell.execute_reply": "2024-11-24T09:28:45.318291Z" + }, + "lines_to_next_cell": 2 + }, "outputs": [ { "name": "stderr", @@ -212,7 +249,7 @@ "array([ 0., 1., 0., 1., nan, nan])" ] }, - "execution_count": 16, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -224,17 +261,19 @@ "\n", "regressor = LogisticRegression()\n", "wrapper = SafeInferenceWrapper(regressor, safe_inference_mode=True)\n", - "wrapper.fit(fps, [0,1,0,1,0,1])\n", - "wrapper.predict(fps)\n" + "wrapper.fit(fps, [0, 1, 0, 1, 0, 1])\n", + "wrapper.predict(fps)" ] }, { "cell_type": "markdown", + "id": "7aa1223f", "metadata": {}, "source": [] }, { "cell_type": "markdown", + "id": "f08d26d5", "metadata": {}, "source": [ "The prediction went fine both in fit and in prediction, where the result shows `nan` for the invalid entries. However, please note fit in sage_inference_mode is not recommended in a training session, but you are warned and not blocked, because maybe you know what you do and do it on purpose.\n", @@ -246,8 +285,16 @@ }, { "cell_type": "code", - "execution_count": 17, - "metadata": {}, + "execution_count": 6, + "id": "51436aa8", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:28:45.321557Z", + "iopub.status.busy": "2024-11-24T09:28:45.321253Z", + "iopub.status.idle": "2024-11-24T09:28:45.333442Z", + "shell.execute_reply": "2024-11-24T09:28:45.332830Z" + } + }, "outputs": [ { "name": "stdout", @@ -259,33 +306,21 @@ "With safe inference mode:\n", "[ 1. 0. 1. 0. nan nan]\n" ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n" - ] } ], "source": [ "from scikit_mol.safeinference import set_safe_inference_mode\n", "from sklearn.pipeline import Pipeline\n", "\n", - "pipe = Pipeline([\n", - " (\"smi2mol\", SmilesToMolTransformer()),\n", - " (\"mfp\", MorganFingerprintTransformer(radius=2, nBits=25)),\n", - " (\"safe_regressor\", SafeInferenceWrapper(LogisticRegression()))\n", - "])\n", + "pipe = Pipeline(\n", + " [\n", + " (\"smi2mol\", SmilesToMolTransformer()),\n", + " (\"mfp\", MorganFingerprintTransformer(radius=2, fpSize=25)),\n", + " (\"safe_regressor\", SafeInferenceWrapper(LogisticRegression())),\n", + " ]\n", + ")\n", "\n", - "pipe.fit(smiles, [1,0,1,0])\n", + "pipe.fit(smiles, [1, 0, 1, 0])\n", "\n", "print(\"Without safe inference mode:\")\n", "try:\n", @@ -302,6 +337,7 @@ }, { "cell_type": "markdown", + "id": "cf53d58f", "metadata": {}, "source": [ "We see that the prediction fail without safe inference mode, and proceeds when it's conveniently set by the `set_safe_inference_mode` utility. The model is now ready for save and reuse in a more failsafe manner :-)" @@ -309,6 +345,7 @@ }, { "cell_type": "markdown", + "id": "685e22fd", "metadata": {}, "source": [ "## Combining safe_inference_mode with pandas output\n", @@ -317,19 +354,17 @@ }, { "cell_type": "code", - "execution_count": 18, - "metadata": {}, + "execution_count": 7, + "id": "b8dbd88c", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:28:45.336071Z", + "iopub.status.busy": "2024-11-24T09:28:45.335859Z", + "iopub.status.idle": "2024-11-24T09:28:45.351873Z", + "shell.execute_reply": "2024-11-24T09:28:45.351251Z" + } + }, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n" - ] - }, { "data": { "text/html": [ @@ -504,7 +539,7 @@ "[4 rows x 25 columns]" ] }, - "execution_count": 18, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -520,6 +555,7 @@ }, { "cell_type": "markdown", + "id": "092ca859", "metadata": {}, "source": [ "Then lets see if we transform a batch with an invalid molecule:" @@ -527,19 +563,17 @@ }, { "cell_type": "code", - "execution_count": 19, - "metadata": {}, + "execution_count": 8, + "id": "710ceeb0", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:28:45.354427Z", + "iopub.status.busy": "2024-11-24T09:28:45.354176Z", + "iopub.status.idle": "2024-11-24T09:28:45.377892Z", + "shell.execute_reply": "2024-11-24T09:28:45.377253Z" + } + }, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n" - ] - }, { "data": { "text/html": [ @@ -770,7 +804,7 @@ "[6 rows x 25 columns]" ] }, - "execution_count": 19, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } @@ -782,24 +816,31 @@ }, { "cell_type": "markdown", + "id": "b87b46b3", "metadata": {}, "source": [ - "The second output is no longer integers, but floats. As most sklearn models cast input arrays to float32 internally, this difference is likely benign, but that's not guaranteed! Thus if you want to use pandas output for your production models, do check that the final outputs are the same for the valid rows, with and without a single invalid row. Alternatively the dtype for the output of the transformer can be switched to float for consistency." + "The second output is no longer integers, but floats. As most sklearn models cast input arrays to float32 internally, this difference is likely benign, but that's not guaranteed! Thus if you want to use pandas output for your production models, do check that the final outputs are the same for the valid rows, with and without a single invalid row. Alternatively the dtype for the output of the transformer can be switched to float for consistency if its supported by the transformer." ] }, { "cell_type": "code", - "execution_count": 20, - "metadata": {}, + "execution_count": 9, + "id": "bbfe1ec0", + "metadata": { + "execution": { + "iopub.execute_input": "2024-11-24T09:28:45.380434Z", + "iopub.status.busy": "2024-11-24T09:28:45.380233Z", + "iopub.status.idle": "2024-11-24T09:28:45.393639Z", + "shell.execute_reply": "2024-11-24T09:28:45.393095Z" + } + }, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n", - "[17:02:50] DEPRECATION WARNING: please use MorganGenerator\n" + "/home/esben/git/scikit-mol/scikit_mol/fingerprints/morgan.py:69: DeprecationWarning: dtype is no longer supported, due to move to generator based fingerprints\n", + " self.dtype = dtype\n" ] }, { @@ -849,99 +890,99 @@ " \n", " \n", " 0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 1.0\n", - " 1.0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 1\n", + " 1\n", " ...\n", - " 0.0\n", - " 1.0\n", - " 0.0\n", - " 1.0\n", - " 1.0\n", - " 1.0\n", - " 0.0\n", - " 1.0\n", - " 1.0\n", - " 0.0\n", + " 0\n", + " 1\n", + " 0\n", + " 1\n", + " 1\n", + " 1\n", + " 0\n", + " 1\n", + " 1\n", + " 0\n", " \n", " \n", " 1\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 1.0\n", - " 1.0\n", - " 1.0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 1\n", + " 1\n", + " 1\n", " ...\n", - " 0.0\n", - " 1.0\n", - " 0.0\n", - " 0.0\n", - " 1.0\n", - " 1.0\n", - " 0.0\n", - " 0.0\n", - " 1.0\n", - " 0.0\n", + " 0\n", + " 1\n", + " 0\n", + " 0\n", + " 1\n", + " 1\n", + " 0\n", + " 0\n", + " 1\n", + " 0\n", " \n", " \n", " 2\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 1.0\n", - " 1.0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 1\n", + " 1\n", " ...\n", - " 0.0\n", - " 1.0\n", - " 0.0\n", - " 1.0\n", - " 1.0\n", - " 1.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", + " 0\n", + " 1\n", + " 0\n", + " 1\n", + " 1\n", + " 1\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", " \n", " \n", " 3\n", - " 1.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 0.0\n", - " 1.0\n", - " 1.0\n", + " 1\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 0\n", + " 1\n", + " 1\n", " ...\n", - " 0.0\n", - " 1.0\n", - " 0.0\n", - " 0.0\n", - " 1.0\n", - " 1.0\n", - " 0.0\n", - " 1.0\n", - " 0.0\n", - " 1.0\n", + " 0\n", + " 1\n", + " 0\n", + " 0\n", + " 1\n", + " 1\n", + " 0\n", + " 1\n", + " 0\n", + " 1\n", " \n", " \n", "\n", @@ -950,39 +991,41 @@ ], "text/plain": [ " fp_morgan_1 fp_morgan_2 fp_morgan_3 fp_morgan_4 fp_morgan_5 \\\n", - "0 0.0 0.0 0.0 0.0 0.0 \n", - "1 0.0 0.0 0.0 0.0 0.0 \n", - "2 0.0 0.0 0.0 0.0 0.0 \n", - "3 1.0 0.0 0.0 0.0 0.0 \n", + "0 0 0 0 0 0 \n", + "1 0 0 0 0 0 \n", + "2 0 0 0 0 0 \n", + "3 1 0 0 0 0 \n", "\n", " fp_morgan_6 fp_morgan_7 fp_morgan_8 fp_morgan_9 fp_morgan_10 ... \\\n", - "0 0.0 0.0 0.0 1.0 1.0 ... \n", - "1 0.0 0.0 1.0 1.0 1.0 ... \n", - "2 0.0 0.0 0.0 1.0 1.0 ... \n", - "3 0.0 0.0 0.0 1.0 1.0 ... \n", + "0 0 0 0 1 1 ... \n", + "1 0 0 1 1 1 ... \n", + "2 0 0 0 1 1 ... \n", + "3 0 0 0 1 1 ... \n", "\n", " fp_morgan_16 fp_morgan_17 fp_morgan_18 fp_morgan_19 fp_morgan_20 \\\n", - "0 0.0 1.0 0.0 1.0 1.0 \n", - "1 0.0 1.0 0.0 0.0 1.0 \n", - "2 0.0 1.0 0.0 1.0 1.0 \n", - "3 0.0 1.0 0.0 0.0 1.0 \n", + "0 0 1 0 1 1 \n", + "1 0 1 0 0 1 \n", + "2 0 1 0 1 1 \n", + "3 0 1 0 0 1 \n", "\n", " fp_morgan_21 fp_morgan_22 fp_morgan_23 fp_morgan_24 fp_morgan_25 \n", - "0 1.0 0.0 1.0 1.0 0.0 \n", - "1 1.0 0.0 0.0 1.0 0.0 \n", - "2 1.0 0.0 0.0 0.0 0.0 \n", - "3 1.0 0.0 1.0 0.0 1.0 \n", + "0 1 0 1 1 0 \n", + "1 1 0 0 1 0 \n", + "2 1 0 0 0 0 \n", + "3 1 0 1 0 1 \n", "\n", "[4 rows x 25 columns]" ] }, - "execution_count": 20, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "mfp_float = MorganFingerprintTransformer(radius=2, nBits=25, safe_inference_mode=True, dtype=np.float32)\n", + "mfp_float = MorganFingerprintTransformer(\n", + " radius=2, fpSize=25, safe_inference_mode=True, dtype=np.float32\n", + ")\n", "mfp_float.set_output(transform=\"pandas\")\n", "fps = mfp_float.transform(mols)\n", "fps" @@ -990,6 +1033,7 @@ }, { "cell_type": "markdown", + "id": "2c7b382c", "metadata": {}, "source": [ "I hope this new feature of Scikit-Mol will make it even easier to handle models, even when used in environments without SMILES or molecule validity guarantees." @@ -1019,5 +1063,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 5 } diff --git a/notebooks/11_safe_inference.py b/notebooks/11_safe_inference.py index 83d4d99..14b97f7 100644 --- a/notebooks/11_safe_inference.py +++ b/notebooks/11_safe_inference.py @@ -26,9 +26,10 @@ from rdkit import Chem from scikit_mol.conversions import SmilesToMolTransformer -#We have some deprecation warnings, we are adressing them, but they just distract from this demonstration +# We have some deprecation warnings, we are adressing them, but they just distract from this demonstration import warnings -warnings.filterwarnings("ignore", category=DeprecationWarning) + +warnings.filterwarnings("ignore", category=DeprecationWarning) smiles = ["C1=CC=C(C=C1)F", "C1=CC=C(C=C1)O", "C1=CC=C(C=C1)N", "C1=CC=C(C=C1)Cl"] smiles_with_invalid = smiles + ["N(C)(C)(C)C", "I'm not a SMILES"] @@ -57,7 +58,7 @@ # %% from scikit_mol.fingerprints import MorganFingerprintTransformer -mfp = MorganFingerprintTransformer(radius=2, nBits=25, safe_inference_mode=True) +mfp = MorganFingerprintTransformer(radius=2, fpSize=25, safe_inference_mode=True) fps = mfp.transform(mols_with_invalid) fps @@ -72,7 +73,7 @@ regressor = LogisticRegression() wrapper = SafeInferenceWrapper(regressor, safe_inference_mode=True) -wrapper.fit(fps, [0,1,0,1,0,1]) +wrapper.fit(fps, [0, 1, 0, 1, 0, 1]) wrapper.predict(fps) @@ -90,13 +91,15 @@ from scikit_mol.safeinference import set_safe_inference_mode from sklearn.pipeline import Pipeline -pipe = Pipeline([ - ("smi2mol", SmilesToMolTransformer()), - ("mfp", MorganFingerprintTransformer(radius=2, nBits=25)), - ("safe_regressor", SafeInferenceWrapper(LogisticRegression())) -]) +pipe = Pipeline( + [ + ("smi2mol", SmilesToMolTransformer()), + ("mfp", MorganFingerprintTransformer(radius=2, fpSize=25)), + ("safe_regressor", SafeInferenceWrapper(LogisticRegression())), + ] +) -pipe.fit(smiles, [1,0,1,0]) +pipe.fit(smiles, [1, 0, 1, 0]) print("Without safe inference mode:") try: @@ -133,10 +136,12 @@ fps # %% [markdown] -# The second output is no longer integers, but floats. As most sklearn models cast input arrays to float32 internally, this difference is likely benign, but that's not guaranteed! Thus if you want to use pandas output for your production models, do check that the final outputs are the same for the valid rows, with and without a single invalid row. Alternatively the dtype for the output of the transformer can be switched to float for consistency. +# The second output is no longer integers, but floats. As most sklearn models cast input arrays to float32 internally, this difference is likely benign, but that's not guaranteed! Thus if you want to use pandas output for your production models, do check that the final outputs are the same for the valid rows, with and without a single invalid row. Alternatively the dtype for the output of the transformer can be switched to float for consistency if its supported by the transformer. # %% -mfp_float = MorganFingerprintTransformer(radius=2, nBits=25, safe_inference_mode=True, dtype=np.float32) +mfp_float = MorganFingerprintTransformer( + radius=2, fpSize=25, safe_inference_mode=True, dtype=np.float32 +) mfp_float.set_output(transform="pandas") fps = mfp_float.transform(mols) fps