datamol

Par mkurman · zorai

Enveloppe Pythonique autour de RDKit avec une interface simplifiée et des paramètres par défaut adaptés. Recommandée pour la découverte de médicaments standard, notamment le parsing de SMILES, la standardisation, les descripteurs, les fingerprints, le clustering, les conformères 3D et le traitement parallèle. Retourne des objets natifs `rdkit.Chem.Mol`. Pour un contrôle avancé ou des paramètres personnalisés, utilisez RDKit directement.

npx skills add https://github.com/mkurman/zorai --skill datamol

Compétence Cheminformatique Datamol

Vue d'ensemble

Datamol est une bibliothèque Python qui fournit une couche d'abstraction légère et pythonique au-dessus de RDKit pour la cheminformatique moléculaire. Simplifiez les opérations moléculaires complexes avec des valeurs par défaut sensées, une parallélisation efficace et des capacités I/O modernes. Tous les objets moléculaires sont des instances natives rdkit.Chem.Mol, garantissant une compatibilité complète avec l'écosystème RDKit.

Capacités clés :

  • Conversion de formats moléculaires (SMILES, SELFIES, InChI)
  • Standardisation et assainissement de structures
  • Descripteurs moléculaires et empreintes digitales
  • Génération et analyse de conformères 3D
  • Clustering et sélection de diversité
  • Analyse de squelettes et de fragments
  • Application de réactions chimiques
  • Visualisation et alignement
  • Traitement par lots avec parallélisation
  • Support du stockage cloud via fsspec

Installation et configuration

Guide pour installer datamol :

uv pip install datamol

Convention d'importation :

import datamol as dm

Workflows principaux

1. Manipulation basique de molécules

Créer des molécules à partir de SMILES :

import datamol as dm

# Molécule unique
mol = dm.to_mol("CCO")  # Ethanol

# À partir d'une liste de SMILES
smiles_list = ["CCO", "c1ccccc1", "CC(=O)O"]
mols = [dm.to_mol(smi) for smi in smiles_list]

# Gestion des erreurs
mol = dm.to_mol("invalid_smiles")  # Retourne None
if mol is None:
    print("Failed to parse SMILES")

Convertir des molécules en SMILES :

# SMILES canonique
smiles = dm.to_smiles(mol)

# SMILES isomérique (inclut la stéréochimie)
smiles = dm.to_smiles(mol, isomeric=True)

# Autres formats
inchi = dm.to_inchi(mol)
inchikey = dm.to_inchikey(mol)
selfies = dm.to_selfies(mol)

Standardisation et assainissement (toujours recommandé pour les molécules fournies par l'utilisateur) :

# Assainir la molécule
mol = dm.sanitize_mol(mol)

# Standardisation complète (recommandée pour les ensembles de données)
mol = dm.standardize_mol(
    mol,
    disconnect_metals=True,
    normalize=True,
    reionize=True
)

# Pour les chaînes SMILES directement
clean_smiles = dm.standardize_smiles(smiles)

2. Lecture et écriture de fichiers moléculaires

Consultez references/io_module.md pour la documentation complète de l'I/O.

Lecture de fichiers :

# Fichiers SDF (les plus courants en chimie)
df = dm.read_sdf("compounds.sdf", mol_column='mol')

# Fichiers SMILES
df = dm.read_smi("molecules.smi", smiles_column='smiles', mol_column='mol')

# CSV avec colonne SMILES
df = dm.read_csv("data.csv", smiles_column="SMILES", mol_column="mol")

# Fichiers Excel
df = dm.read_excel("compounds.xlsx", sheet_name=0, mol_column="mol")

# Lecteur universel (détection automatique du format)
df = dm.open_df("file.sdf")  # Fonctionne avec .sdf, .csv, .xlsx, .parquet, .json

Écriture de fichiers :

# Enregistrer en SDF
dm.to_sdf(mols, "output.sdf")
# Ou à partir d'un DataFrame
dm.to_sdf(df, "output.sdf", mol_column="mol")

# Enregistrer en fichier SMILES
dm.to_smi(mols, "output.smi")

# Excel avec images de molécules rendues
dm.to_xlsx(df, "output.xlsx", mol_columns=["mol"])

Support des fichiers distants (S3, GCS, HTTP) :

# Lire depuis le stockage cloud
df = dm.read_sdf("s3://bucket/compounds.sdf")
df = dm.read_csv("https://example.com/data.csv")

# Écrire vers le stockage cloud
dm.to_sdf(mols, "s3://bucket/output.sdf")

3. Descripteurs et propriétés moléculaires

Consultez references/descriptors_viz.md pour la documentation détaillée des descripteurs.

Calcul de descripteurs pour une molécule unique :

# Obtenir l'ensemble standard de descripteurs
descriptors = dm.descriptors.compute_many_descriptors(mol)
# Retourne : {'mw': 46.07, 'logp': -0.03, 'hbd': 1, 'hba': 1,
#             'tpsa': 20.23, 'n_aromatic_atoms': 0, ...}

Calcul de descripteurs par lot (recommandé pour les ensembles de données) :

# Calculer pour toutes les molécules en parallèle
desc_df = dm.descriptors.batch_compute_many_descriptors(
    mols,
    n_jobs=-1,      # Utiliser tous les cœurs CPU
    progress=True   # Afficher la barre de progression
)

Descripteurs spécifiques :

# Aromaticité
n_aromatic = dm.descriptors.n_aromatic_atoms(mol)
aromatic_ratio = dm.descriptors.n_aromatic_atoms_proportion(mol)

# Stéréochimie
n_stereo = dm.descriptors.n_stereo_centers(mol)
n_unspec = dm.descriptors.n_stereo_centers_unspecified(mol)

# Flexibilité
n_rigid = dm.descriptors.n_rigid_bonds(mol)

Filtrage de ressemblance aux médicaments (Règle de Lipinski des Cinq) :

# Filtrer les composés
def is_druglike(mol):
    desc = dm.descriptors.compute_many_descriptors(mol)
    return (
        desc['mw'] <= 500 and
        desc['logp'] <= 5 and
        desc['hbd'] <= 5 and
        desc['hba'] <= 10
    )

druglike_mols = [mol for mol in mols if is_druglike(mol)]

4. Empreintes digitales moléculaires et similarité

Génération d'empreintes digitales :

# ECFP (Extended Connectivity Fingerprint, par défaut)
fp = dm.to_fp(mol, fp_type='ecfp', radius=2, n_bits=2048)

# Autres types d'empreintes
fp_maccs = dm.to_fp(mol, fp_type='maccs')
fp_topological = dm.to_fp(mol, fp_type='topological')
fp_atompair = dm.to_fp(mol, fp_type='atompair')

Calculs de similarité :

# Distances par paires dans un ensemble
distance_matrix = dm.pdist(mols, n_jobs=-1)

# Distances entre deux ensembles
distances = dm.cdist(query_mols, library_mols, n_jobs=-1)

# Trouver les molécules les plus similaires
from scipy.spatial.distance import squareform
dist_matrix = squareform(dm.pdist(mols))
# Distance inférieure = similarité supérieure (distance Tanimoto = 1 - similarité Tanimoto)

5. Clustering et sélection de diversité

Consultez references/core_api.md pour les détails du clustering.

Clustering Butina :

# Grouper les molécules par similarité structurale
clusters = dm.cluster_mols(
    mols,
    cutoff=0.2,    # Seuil de distance Tanimoto (0=identique, 1=complètement différent)
    n_jobs=-1      # Traitement parallèle
)

# Chaque cluster est une liste d'indices de molécules
for i, cluster in enumerate(clusters):
    print(f"Cluster {i}: {len(cluster)} molecules")
    cluster_mols = [mols[idx] for idx in cluster]

Important : Le clustering Butina construit une matrice de distance complète - adapté pour ~1 000 molécules, pas pour 10 000+.

Sélection de diversité :

# Sélectionner un sous-ensemble diversifié
diverse_mols = dm.pick_diverse(
    mols,
    npick=100  # Sélectionner 100 molécules diversifiées
)

# Sélectionner les centroïdes des clusters
centroids = dm.pick_centroids(
    mols,
    npick=50   # Sélectionner 50 molécules représentatives
)

6. Analyse de squelettes

Consultez references/fragments_scaffolds.md pour la documentation complète des squelettes.

Extraction de squelettes Murcko :

# Obtenir le squelette Bemis-Murcko (structure de base)
scaffold = dm.to_scaffold_murcko(mol)
scaffold_smiles = dm.to_smiles(scaffold)

Analyse basée sur les squelettes :

# Grouper les composés par squelette
from collections import Counter

scaffolds = [dm.to_scaffold_murcko(mol) for mol in mols]
scaffold_smiles = [dm.to_smiles(s) for s in scaffolds]

# Compter la fréquence des squelettes
scaffold_counts = Counter(scaffold_smiles)
most_common = scaffold_counts.most_common(10)

# Créer un mappage squelette-vers-molécules
scaffold_groups = {}
for mol, scaf_smi in zip(mols, scaffold_smiles):
    if scaf_smi not in scaffold_groups:
        scaffold_groups[scaf_smi] = []
    scaffold_groups[scaf_smi].append(mol)

Partage train/test basé sur les squelettes (pour le ML) :

# Assurer que les ensembles train et test ont des squelettes différents
scaffold_to_mols = {}
for mol, scaf in zip(mols, scaffold_smiles):
    if scaf not in scaffold_to_mols:
        scaffold_to_mols[scaf] = []
    scaffold_to_mols[scaf].append(mol)

# Partager les squelettes en train/test
import random
scaffolds = list(scaffold_to_mols.keys())
random.shuffle(scaffolds)
split_idx = int(0.8 * len(scaffolds))
train_scaffolds = scaffolds[:split_idx]
test_scaffolds = scaffolds[split_idx:]

# Obtenir les molécules pour chaque partition
train_mols = [mol for scaf in train_scaffolds for mol in scaffold_to_mols[scaf]]
test_mols = [mol for scaf in test_scaffolds for mol in scaffold_to_mols[scaf]]

7. Fragmentation moléculaire

Consultez references/fragments_scaffolds.md pour les détails de fragmentation.

Fragmentation BRICS (16 types de liaisons) :

# Fragmenter la molécule
fragments = dm.fragment.brics(mol)
# Retourne : ensemble de SMILES de fragments avec points d'attachement comme '[1*]CCN'

Fragmentation RECAP (11 types de liaisons) :

fragments = dm.fragment.recap(mol)

Analyse de fragments :

# Trouver les fragments courants dans la bibliothèque de composés
from collections import Counter

all_fragments = []
for mol in mols:
    frags = dm.fragment.brics(mol)
    all_fragments.extend(frags)

fragment_counts = Counter(all_fragments)
common_frags = fragment_counts.most_common(20)

# Score basé sur les fragments
def fragment_score(mol, reference_fragments):
    mol_frags = dm.fragment.brics(mol)
    overlap = mol_frags.intersection(reference_fragments)
    return len(overlap) / len(mol_frags) if mol_frags else 0

8. Génération de conformères 3D

Consultez references/conformers_module.md pour la documentation détaillée des conformères.

Génération de conformères :

# Générer des conformères 3D
mol_3d = dm.conformers.generate(
    mol,
    n_confs=50,           # Nombre à générer (auto si None)
    rms_cutoff=0.5,       # Filtrer les conformères similaires (Ångströms)
    minimize_energy=True,  # Minimiser avec le champ de force UFF
    method='ETKDGv3'      # Méthode d'incorporation (recommandée)
)

# Accéder aux conformères
n_conformers = mol_3d.GetNumConformers()
conf = mol_3d.GetConformer(0)  # Obtenir le premier conformère
positions = conf.GetPositions()  # Tableau Nx3 des coordonnées des atomes

Clustering de conformères :

# Grouper les conformères par RMSD
clusters = dm.conformers.cluster(
    mol_3d,
    rms_cutoff=1.0,
    centroids=False
)

# Obtenir les conformères représentatifs
centroids = dm.conformers.return_centroids(mol_3d, clusters)

Calcul SASA :

# Calculer la surface accessible au solvant
sasa_values = dm.conformers.sasa(mol_3d, n_jobs=-1)

# Accéder à SASA à partir des propriétés du conformère
conf = mol_3d.GetConformer(0)
sasa = conf.GetDoubleProp('rdkit_free_sasa')

9. Visualisation

Consultez references/descriptors_viz.md pour la documentation de visualisation.

Grille de molécules basique :

# Visualiser les molécules
dm.viz.to_image(
    mols[:20],
    legends=[dm.to_smiles(m) for m in mols[:20]],
    n_cols=5,
    mol_size=(300, 300)
)

# Enregistrer dans un fichier
dm.viz.to_image(mols, outfile="molecules.png")

# SVG pour les publications
dm.viz.to_image(mols, outfile="molecules.svg", use_svg=True)

Visualisation alignée (pour l'analyse SAR) :

# Aligner les molécules par sous-structure commune
dm.viz.to_image(
    similar_mols,
    align=True,  # Activer l'alignement MCS
    legends=activity_labels,
    n_cols=4
)

Mise en évidence de sous-structures :

# Mettre en évidence des atomes et liaisons spécifiques
dm.viz.to_image(
    mol,
    highlight_atom=[0, 1, 2, 3],  # Indices des atomes
    highlight_bond=[0, 1, 2]      # Indices des liaisons
)

Visualisation de conformères :

# Afficher plusieurs conformères
dm.viz.conformers(
    mol_3d,
    n_confs=10,
    align_conf=True,
    n_cols=3
)

10. Réactions chimiques

Consultez references/reactions_data.md pour la documentation des réactions.

Application de réactions :

from rdkit.Chem import rdChemReactions

# Définir la réaction à partir de SMARTS
rxn_smarts = '[C:1](=[O:2])[OH:3]>>[C:1](=[O:2])[Cl:3]'
rxn = rdChemReactions.ReactionFromSmarts(rxn_smarts)

# Appliquer à la molécule
reactant = dm.to_mol("CC(=O)O")  # Acide acétique
product = dm.reactions.apply_reaction(
    rxn,
    (reactant,),
    sanitize=True
)

# Convertir en SMILES
product_smiles = dm.to_smiles(product)

Application de réaction par lot :

# Appliquer la réaction à la bibliothèque
products = []
for mol in reactant_mols:
    try:
        prod = dm.reactions.apply_reaction(rxn, (mol,))
        if prod is not None:
            products.append(prod)
    except Exception as e:
        print(f"Reaction failed: {e}")

Parallélisation

Datamol inclut la parallélisation intégrée pour de nombreuses opérations. Utilisez le paramètre n_jobs :

  • n_jobs=1 : Séquentiel (pas de parallélisation)
  • n_jobs=-1 : Utiliser tous les cœurs CPU disponibles
  • n_jobs=4 : Utiliser 4 cœurs

Fonctions supportant la parallélisation :

  • dm.read_sdf(..., n_jobs=-1)
  • dm.descriptors.batch_compute_many_descriptors(..., n_jobs=-1)
  • dm.cluster_mols(..., n_jobs=-1)
  • dm.pdist(..., n_jobs=-1)
  • dm.conformers.sasa(..., n_jobs=-1)

Barres de progression : De nombreuses opérations par lot supportent le paramètre progress=True.

Workflows courants et motifs

Pipeline complet : Chargement de données → Filtrage → Analyse

import datamol as dm
import pandas as pd

# 1. Charger les molécules
df = dm.read_sdf("compounds.sdf")

# 2. Standardiser
df['mol'] = df['mol'].apply(lambda m: dm.standardize_mol(m) if m else None)
df = df[df['mol'].notna()]  # Supprimer les molécules non traitées

# 3. Calculer les descripteurs
desc_df = dm.descriptors.batch_compute_many_descriptors(
    df['mol'].tolist(),
    n_jobs=-1,
    progress=True
)

# 4. Filtrer par ressemblance aux médicaments
druglike = (
    (desc_df['mw'] <= 500) &
    (desc_df['logp'] <= 5) &
    (desc_df['hbd'] <= 5) &
    (desc_df['hba'] <= 10)
)
filtered_df = df[druglike]

# 5. Grouper et sélectionner un sous-ensemble diversifié
diverse_mols = dm.pick_diverse(
    filtered_df['mol'].tolist(),
    npick=100
)

# 6. Visualiser les résultats
dm.viz.to_image(
    diverse_mols,
    legends=[dm.to_smiles(m) for m in diverse_mols],
    outfile="diverse_compounds.png",
    n_cols=10
)

Analyse de relation structure-activité (SAR)

# Grouper par squelette
scaffolds = [dm.to_scaffold_murcko(mol) for mol in mols]
scaffold_smiles = [dm.to_smiles(s) for s in scaffolds]

# Créer un DataFrame avec les activités
sar_df = pd.DataFrame({
    'mol': mols,
    'scaffold': scaffold_smiles,
    'activity': activities  # Données d'activité fournies par l'utilisateur
})

# Analyser chaque série de squelettes
for scaffold, group in sar_df.groupby('scaffold'):
    if len(group) >= 3:  # Besoin de plusieurs exemples
        print(f"\nScaffold: {scaffold}")
        print(f"Count: {len(group)}")
        print(f"Activity range: {group['activity'].min():.2f} - {group['activity'].max():.2f}")

        # Visualiser avec les activités comme légendes
        dm.viz.to_image(
            group['mol'].tolist(),
            legends=[f"Activity: {act:.2f}" for act in group['activity']],
            align=True  # Aligner par sous-structure commune
        )

Pipeline de criblage virtuel

# 1. Générer les empreintes pour la requête et la bibliothèque
query_fps = [dm.to_fp(mol) for mol in query_actives]
library_fps = [dm.to_fp(mol) for mol in library_mols]

# 2. Calculer les similarités
from scipy.spatial.distance import cdist
import numpy as np

distances = dm.cdist(query_actives, library_mols, n_jobs=-1)

# 3. Trouver les correspondances les plus proches (distance min à n'importe quelle requête)
min_distances = distances.min(axis=0)
similarities = 1 - min_distances  # Convertir la distance en similarité

# 4. Classer et sélectionner les meilleures correspondances
top_indices = np.argsort(similarities)[::-1][:100]  # Top 100
top_hits = [library_mols[i] for i in top_indices]
top_scores = [similarities[i] for i in top_indices]

# 5. Visualiser les correspondances
dm.viz.to_image(
    top_hits[:20],
    legends=[f"Sim: {score:.3f}" for score in top_scores[:20]],
    outfile="screening_hits.png"
)

Documentation de référence

Pour la documentation complète de l'API, consultez ces fichiers de référence :

  • references/core_api.md : Fonctions de l'espace de noms principal (conversions, standardisation, empreintes, clustering)
  • references/io_module.md : Opérations d'I/O de fichiers (lecture/écriture SDF, CSV, Excel, fichiers distants)
  • references/conformers_module.md : Génération de conformères 3D, clustering, calculs SASA
  • references/descriptors_viz.md : Descripteurs moléculaires et fonctions de visualisation
  • references/fragments_scaffolds.md : Extraction de squelettes, fragmentation BRICS/RECAP
  • references/reactions_data.md : Réactions chimiques et ensembles de données jouets

Bonnes pratiques

  1. Toujours standardiser les molécules provenant de sources externes :

    mol = dm.standardize_mol(mol, disconnect_metals=True, normalize=True, reionize=True)
  2. Vérifier les valeurs None après l'analyse de molécules :

    mol = dm.to_mol(smiles)
    if mol is None:
        # Gérer les SMILES invalides
  3. Utiliser le traitement parallèle pour les grands ensembles de données :

    result = dm.operation(..., n_jobs=-1, progress=True)
  4. Exploiter fsspec pour le stockage cloud :

    df = dm.read_sdf("s3://bucket/compounds.sdf")
  5. Utiliser les empreintes appropriées pour la similarité :

    • ECFP (Morgan) : Usage général, similarité structurale
    • MACCS : Rapide, espace de caractéristiques plus petit
    • Paires d'atomes : Considère les paires d'atomes et les distances
  6. Considérer les limitations d'échelle :

    • Clustering Butina : ~1 000 molécules (matrice de distance complète)
    • Pour les ensembles plus grands : Utiliser la sélection de diversité ou des méthodes hiérarchiques
  7. Partage par squelette pour le ML : Assurer une séparation correcte train/test par squelette

  8. Aligner les molécules lors de la visualisation des séries SAR

Gestion des erreurs

# Création sûre de molécules
def safe_to_mol(smiles):
    try:
        mol = dm.to_mol(smiles)
        if mol is not None:
            mol = dm.standardize_mol(mol)
        return mol
    except Exception as e:
        print(f"Failed to process {smiles}: {e}")
        return None

# Traitement par lot sûr
valid_mols = []
for smiles in smiles_list:
    mol = safe_to_mol(smiles)
    if mol is not None:
        valid_mols.append(mol)

Intégration avec l'apprentissage automatique

# Génération de caractéristiques
X = np.array([dm.to_fp(mol) for mol in mols])

# Ou descripteurs
desc_df = dm.descriptors.batch_compute_many_descriptors(mols, n_jobs=-1)
X = desc_df.values

# Entraîner le modèle
from sklearn.ensemble import RandomForestRegressor
model = RandomForestRegressor()
model.fit(X, y_target)

# Prédire
predictions = model.predict(X_test)

Dépannage

Problème : L'analyse de molécules échoue

  • Solution : Utiliser d'abord dm.standardize_smiles() ou essayer dm.fix_mol()

Problème : Erreurs de mémoire avec le clustering

  • Solution : Utiliser dm.pick_diverse() au lieu du clustering complet pour les grands ensembles

Problème : Génération de conformères lente

  • Solution : Réduire n_confs ou augmenter rms_cutoff pour générer moins de conformères

Problème : L'accès aux fichiers distants échoue

  • Solution : Assurer que fsspec et les bibliothèques appropriées du fournisseur cloud sont installées (s3fs, gcsfs, etc.)

Ressources supplémentaires

Skills similaires