Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Pythonic wrapper around RDKit with simplified interface and sensible defaults. Preferred for standard drug discovery including SMILES parsing, standardization, descriptors, fingerprints, clustering, 3D conformers, parallel processing. Returns native rdkit.Chem.Mol objects. For advanced control or custom parameters, use rdkit directly.
.claude/skills/lingxling-datamol/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 113% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 90% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 53% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 311% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 95% | 0% |
Datamol is a Python library that provides a lightweight, Pythonic abstraction layer over RDKit for molecular cheminformatics. Simplify complex molecular operations with sensible defaults, efficient parallelization, and modern I/O capabilities. All molecular objects are native rdkit.Chem.Mol instances, ensuring full compatibility with the RDKit ecosystem.
Version note: Examples target datamol 0.12.x (PyPI stable: 0.12.5, June 2024). Since 0.10.0, modules are lazy-loaded by default (set DATAMOL_DISABLE_LAZY_LOADING=1 to disable). Since 0.12.2, RDKit is a direct PyPI dependency of datamol. Fingerprints use RDKit's rdFingerprintGenerator API (0.12.5+).
Key capabilities:
Guide users to install datamol:
bashuv pip install datamol
RDKit is installed automatically with datamol. For remote file paths (S3, GCS, HTTP), install the matching fsspec backend:
bashuv pip install s3fs # AWS S3 uv pip install gcsfs # Google Cloud Storage
Import convention:
pythonimport datamol as dm
Creating molecules from SMILES:
pythonimport datamol as dm # Single molecule mol = dm.to_mol("CCO") # Ethanol # From list of SMILES smiles_list = ["CCO", "c1ccccc1", "CC(=O)O"] mols = [dm.to_mol(smi) for smi in smiles_list] # Error handling mol = dm.to_mol("invalid_smiles") # Returns None if mol is None: print("Failed to parse SMILES")
Converting molecules to SMILES:
python# Canonical SMILES smiles = dm.to_smiles(mol) # Isomeric SMILES (includes stereochemistry) smiles = dm.to_smiles(mol, isomeric=True) # Other formats inchi = dm.to_inchi(mol) inchikey = dm.to_inchikey(mol) selfies = dm.to_selfies(mol)
Standardization and sanitization (always recommend for user-provided molecules):
python# Sanitize molecule mol = dm.sanitize_mol(mol) # Full standardization (recommended for datasets) mol = dm.standardize_mol( mol, disconnect_metals=True, normalize=True, reionize=True ) # For SMILES strings directly clean_smiles = dm.standardize_smiles(smiles)
Refer to references/io_module.md for comprehensive I/O documentation.
Reading files:
python# SDF files (most common in chemistry) df = dm.read_sdf("compounds.sdf", mol_column='mol') # SMILES files df = dm.read_smi("molecules.smi", smiles_column='smiles', mol_column='mol') # CSV with SMILES column df = dm.read_csv("data.csv", smiles_column="SMILES", mol_column="mol") # Excel files df = dm.read_excel("compounds.xlsx", sheet_name=0, mol_column="mol") # Universal reader/writer (auto-detects format; supports compression) df = dm.open_df("file.sdf") # .sdf, .csv, .xlsx, .parquet, .json, .gz, etc. dm.save_df(df, "output.parquet")
Writing files:
python# Save as SDF dm.to_sdf(mols, "output.sdf") # Or from DataFrame dm.to_sdf(df, "output.sdf", mol_column="mol") # Save as SMILES file dm.to_smi(mols, "output.smi") # Excel with rendered molecule images dm.to_xlsx(df, "output.xlsx", mol_columns=["mol"])
Remote file support (S3, GCS, HTTP via fsspec):
Only use cloud paths when the user explicitly requests them. Confirm the destination before writing.
python# Read from cloud storage or HTTPS (user-provided URLs only) df = dm.read_sdf("s3://bucket/compounds.sdf") df = dm.read_csv("https://example.com/data.csv") # Write to cloud storage — confirm path with user first dm.to_sdf(mols, "s3://bucket/output.sdf")
Cloud backends read credentials from the standard provider environment (for example AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION, or GOOGLE_APPLICATION_CREDENTIALS). Datamol passes these to fsspec locally; it does not collect or transmit environment variables to third-party endpoints. Scope credential access to the named provider variables only.
Refer to references/descriptors_viz.md for detailed descriptor documentation.
Computing descriptors for a single molecule:
python# Get standard descriptor set descriptors = dm.descriptors.compute_many_descriptors(mol) # Returns: {'mw': 46.07, 'logp': -0.03, 'hbd': 1, 'hba': 1, # 'tpsa': 20.23, 'n_aromatic_atoms': 0, ...}
Batch descriptor computation (recommended for datasets):
python# Compute for all molecules in parallel desc_df = dm.descriptors.batch_compute_many_descriptors( mols, n_jobs=-1, # Use all CPU cores progress=True # Show progress bar )
Specific descriptors:
python# Aromaticity n_aromatic = dm.descriptors.n_aromatic_atoms(mol) aromatic_ratio = dm.descriptors.n_aromatic_atoms_proportion(mol) # Stereochemistry n_stereo = dm.descriptors.n_stereo_centers(mol) n_unspec = dm.descriptors.n_stereo_centers_unspecified(mol) # Flexibility n_rigid = dm.descriptors.n_rigid_bonds(mol)
Drug-likeness filtering (Lipinski's Rule of Five):
python# Filter compounds 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)]
Generating fingerprints:
Datamol defaults to ECFP6 (radius=3, n_bits=2048). Pass radius=2 explicitly for ECFP4.
python# ECFP4 (common in similarity screening) fp = dm.to_fp(mol, fp_type='ecfp', radius=2, n_bits=2048) # Other fingerprint types 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') fp_rdkit = dm.to_fp(mol, fp_type='rdkit')
Similarity calculations:
python# Pairwise distances within a set distance_matrix = dm.pdist(mols, n_jobs=-1) # Distances between two sets distances = dm.cdist(query_mols, library_mols, n_jobs=-1) # Find most similar molecules (scipy is a PyPI package, not a file in this skill) from scipy.spatial.distance import squareform # third-party library dist_matrix = squareform(dm.pdist(mols)) # Lower distance = higher similarity (Tanimoto distance = 1 - Tanimoto similarity)
Refer to references/core_api.md for clustering details.
Butina clustering:
python# Cluster molecules by structural similarity clusters = dm.cluster_mols( mols, cutoff=0.2, # Tanimoto distance threshold (0=identical, 1=completely different) n_jobs=-1 # Parallel processing ) # Each cluster is a list of molecule indices for i, cluster in enumerate(clusters): print(f"Cluster {i}: {len(cluster)} molecules") cluster_mols = [mols[idx] for idx in cluster]
Important: Butina clustering builds a full distance matrix - suitable for ~1000 molecules, not for 10,000+.
Diversity selection:
python# Pick diverse subset diverse_mols = dm.pick_diverse( mols, npick=100 # Select 100 diverse molecules ) # Pick cluster centroids centroids = dm.pick_centroids( mols, npick=50 # Select 50 representative molecules )
Refer to references/fragments_scaffolds.md for complete scaffold documentation.
Extracting Murcko scaffolds:
python# Get Bemis-Murcko scaffold (core structure) scaffold = dm.to_scaffold_murcko(mol) scaffold_smiles = dm.to_smiles(scaffold)
Scaffold-based analysis:
python# Group compounds by scaffold from collections import Counter scaffolds = [dm.to_scaffold_murcko(mol) for mol in mols] scaffold_smiles = [dm.to_smiles(s) for s in scaffolds] # Count scaffold frequency scaffold_counts = Counter(scaffold_smiles) most_common = scaffold_counts.most_common(10) # Create scaffold-to-molecules mapping 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)
Scaffold-based train/test splitting (for ML):
python# Ensure train and test sets have different scaffolds 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) # Split scaffolds into 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:] # Get molecules for each split 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]]
Refer to references/fragments_scaffolds.md for fragmentation details.
BRICS fragmentation (16 bond types):
python# Fragment molecule fragments = dm.fragment.brics(mol) # Returns: set of fragment SMILES with attachment points like '[1*]CCN'
RECAP fragmentation (11 bond types):
pythonfragments = dm.fragment.recap(mol)
Fragment analysis:
python# Find common fragments across compound library 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) # Fragment-based scoring 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
Refer to references/conformers_module.md for detailed conformer documentation.
Generating conformers:
python# Generate 3D conformers mol_3d = dm.conformers.generate( mol, n_confs=50, # Number to generate (auto if None) rms_cutoff=0.5, # Filter similar conformers (Ångströms) minimize_energy=True, # Minimize with UFF force field method='ETKDGv3' # Embedding method (recommended) ) # Access conformers n_conformers = mol_3d.GetNumConformers() conf = mol_3d.GetConformer(0) # Get first conformer positions = conf.GetPositions() # Nx3 array of atom coordinates
Conformer clustering:
python# Cluster conformers by RMSD clusters = dm.conformers.cluster( mol_3d, rms_cutoff=1.0, centroids=False ) # Get representative conformers centroids = dm.conformers.return_centroids(mol_3d, clusters)
SASA calculation:
python# Calculate solvent accessible surface area sasa_values = dm.conformers.sasa(mol_3d, n_jobs=-1) # Access SASA from conformer properties conf = mol_3d.GetConformer(0) sasa = conf.GetDoubleProp('rdkit_free_sasa')
Refer to references/descriptors_viz.md for visualization documentation.
Basic molecule grid:
python# Visualize molecules dm.viz.to_image( mols[:20], legends=[dm.to_smiles(m) for m in mols[:20]], n_cols=5, mol_size=(300, 300) ) # Save to file dm.viz.to_image(mols, outfile="molecules.png") # SVG for publications dm.viz.to_image(mols, outfile="molecules.svg", use_svg=True)
Aligned visualization (for SAR analysis):
python# Align molecules by common substructure dm.viz.to_image( similar_mols, align=True, # Enable MCS alignment legends=activity_labels, n_cols=4 )
Highlighting substructures:
python# Highlight specific atoms and bonds dm.viz.to_image( mol, highlight_atom=[0, 1, 2, 3], # Atom indices highlight_bond=[0, 1, 2] # Bond indices )
Conformer visualization:
python# Display multiple conformers dm.viz.conformers( mol_3d, n_confs=10, align_conf=True, n_cols=3 )
Refer to references/reactions_data.md for reactions documentation.
Applying reactions:
pythonfrom rdkit.Chem import rdChemReactions # Define reaction from SMARTS rxn_smarts = '[C:1](=[O:2])[OH:3]>>[C:1](=[O:2])[Cl:3]' rxn = rdChemReactions.ReactionFromSmarts(rxn_smarts) # Apply to molecule reactant = dm.to_mol("CC(=O)O") # Acetic acid product = dm.reactions.apply_reaction( rxn, (reactant,), sanitize=True ) # Convert to SMILES product_smiles = dm.to_smiles(product)
Batch reaction application:
python# Apply reaction to library 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}")
Datamol includes built-in parallelization for many operations. Use n_jobs parameter:
n_jobs=1: Sequential (no parallelization)n_jobs=-1: Use all available CPU coresn_jobs=4: Use 4 coresFunctions supporting parallelization:
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)Progress bars: Many batch operations support progress=True parameter.
pythonimport datamol as dm import pandas as pd # 1. Load molecules df = dm.read_sdf("compounds.sdf") # 2. Standardize df['mol'] = df['mol'].apply(lambda m: dm.standardize_mol(m) if m else None) df = df[df['mol'].notna()] # Remove failed molecules # 3. Compute descriptors desc_df = dm.descriptors.batch_compute_many_descriptors( df['mol'].tolist(), n_jobs=-1, progress=True ) # 4. Filter by drug-likeness druglike = ( (desc_df['mw'] <= 500) & (desc_df['logp'] <= 5) & (desc_df['hbd'] <= 5) & (desc_df['hba'] <= 10) ) filtered_df = df[druglike] # 5. Cluster and select diverse subset diverse_mols = dm.pick_diverse( filtered_df['mol'].tolist(), npick=100 ) # 6. Visualize results dm.viz.to_image( diverse_mols, legends=[dm.to_smiles(m) for m in diverse_mols], outfile="diverse_compounds.png", n_cols=10 )
python# Group by scaffold scaffolds = [dm.to_scaffold_murcko(mol) for mol in mols] scaffold_smiles = [dm.to_smiles(s) for s in scaffolds] # Create DataFrame with activities sar_df = pd.DataFrame({ 'mol': mols, 'scaffold': scaffold_smiles, 'activity': activities # User-provided activity data }) # Analyze each scaffold series for scaffold, group in sar_df.groupby('scaffold'): if len(group) >= 3: # Need multiple examples print(f"\nScaffold: {scaffold}") print(f"Count: {len(group)}") print(f"Activity range: {group['activity'].min():.2f} - {group['activity'].max():.2f}") # Visualize with activities as legends dm.viz.to_image( group['mol'].tolist(), legends=[f"Activity: {act:.2f}" for act in group['activity']], align=True # Align by common substructure )
pythonimport numpy as np # 1. Calculate Tanimoto distances between query actives and library distances = dm.cdist(query_actives, library_mols, n_jobs=-1) # 3. Find closest matches (min distance to any query) min_distances = distances.min(axis=0) similarities = 1 - min_distances # Convert distance to similarity # 4. Rank and select top hits 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. Visualize hits dm.viz.to_image( top_hits[:20], legends=[f"Sim: {score:.3f}" for score in top_scores[:20]], outfile="screening_hits.png" )
For detailed API documentation, consult these reference files:
references/core_api.md: Core namespace functions (conversions, standardization, fingerprints, clustering)references/io_module.md: File I/O operations (read/write SDF, CSV, Excel, remote files)references/conformers_module.md: 3D conformer generation, clustering, SASA calculationsreferences/descriptors_viz.md: Molecular descriptors and visualization functionsreferences/fragments_scaffolds.md: Scaffold extraction, BRICS/RECAP fragmentationreferences/reactions_data.md: Chemical reactions and toy datasetspython mol = dm.standardize_mol(mol, disconnect_metals=True, normalize=True, reionize=True)
python mol = dm.to_mol(smiles) if mol is None: # Handle invalid SMILES
python result = dm.operation(..., n_jobs=-1, progress=True)
s3fs/gcsfs as needed:python df = dm.read_sdf("s3://bucket/compounds.sdf")
python# Safe molecule creation 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 # Safe batch processing valid_mols = [] for smiles in smiles_list: mol = safe_to_mol(smiles) if mol is not None: valid_mols.append(mol)
Datamol ships with scipy and scikit-learn as dependencies. Import them as normal PyPI packages — they are not scripts bundled in this skill.
pythonimport numpy as np # Feature generation X = np.array([dm.to_fp(mol) for mol in mols]) # Or descriptors desc_df = dm.descriptors.batch_compute_many_descriptors(mols, n_jobs=-1) X = desc_df.values # Train model (scikit-learn PyPI package) from sklearn.ensemble import RandomForestRegressor # third-party library model = RandomForestRegressor() model.fit(X, y_target) # Predict predictions = model.predict(X_test)
Issue: Molecule parsing fails
dm.standardize_smiles() first or try dm.fix_mol()Issue: Memory errors with clustering
dm.pick_diverse() instead of full clustering for large setsIssue: Slow conformer generation
n_confs or increase rms_cutoff to generate fewer conformersIssue: Remote file access fails
uv pip install s3fs or gcsfs) and verify only the provider credentials needed for that backend are set (see Remote file support above)| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 20,509 | 19,267 | -6% | 1 | 1 | 0% | 4,406 | 9,382 | +113% | 0 | 0 | — |
case-02 | fail→pass | 22,635 | 11,411 | -50% | 1 | 1 | 0% | 4,438 | 8,412 | +90% | 0 | 0 | — |
case-03 | fail→pass | 27,642 | 15,094 | -45% | 1 | 1 | 0% | 6,056 | 9,291 | +53% | 0 | 0 | — |
case-04 | fail→pass | 11,394 | 14,603 | +28% | 1 | 1 | 0% | 2,240 | 9,196 | +311% | 0 | 0 | — |
case-05 | fail→pass | 22,187 | 11,794 | -47% | 1 | 1 | 0% | 4,287 | 8,373 | +95% | 0 | 0 | — |
case-06 | fail→pass | 17,200 | 16,550 | -4% | 1 | 1 | 0% | 3,240 | 9,268 | +186% | 0 | 0 | — |
case-07 | fail→pass | 14,657 | 18,806 | +28% | 1 | 1 | 0% | 2,655 | 9,800 | +269% | 0 | 0 | — |
case-08 | fail→pass | 18,437 | 9,068 | -51% | 1 | 1 | 0% | 2,887 | 7,924 | +174% | 0 | 0 | — |
case-09 | fail→pass | 20,205 | 23,556 | +17% | 1 | 1 | 0% | 4,223 | 11,248 | +166% | 0 | 0 | — |
case-10 | fail→pass | 17,914 | 11,396 | -36% | 1 | 1 | 0% | 2,624 | 8,203 | +213% | 0 | 0 | — |
case-11 | fail→pass | 14,164 | 6,705 | -53% | 1 | 1 | 0% | 2,159 | 7,361 | +241% | 0 | 0 | — |
case-12 | fail→pass | 12,676 | 9,566 | -25% | 1 | 1 | 0% | 2,442 | 7,998 | +228% | 0 | 0 | — |
case-13 | fail→pass | 14,308 | 9,151 | -36% | 1 | 1 | 0% | 2,742 | 7,925 | +189% | 0 | 0 | — |
case-14 | fail→pass | 16,356 | 10,711 | -35% | 1 | 1 | 0% | 2,905 | 8,164 | +181% | 0 | 0 | — |
case-15 | fail→pass | 19,802 | 13,598 | -31% | 1 | 1 | 0% | 3,843 | 8,287 | +116% | 0 | 0 | — |
case-16 | fail→pass | 16,515 | 19,220 | +16% | 1 | 1 | 0% | 3,137 | 8,911 | +184% | 0 | 0 | — |
case-17 | fail→pass | 22,386 | 16,529 | -26% | 1 | 1 | 0% | 4,444 | 8,552 | +92% | 0 | 0 | — |
case-18 | fail→pass | 28,500 | 12,910 | -55% | 1 | 1 | 0% | 5,854 | 8,331 | +42% | 0 | 0 | — |
case-19 | fail→pass | 14,516 | 7,280 | -50% | 1 | 1 | 0% | 2,788 | 7,179 | +157% | 0 | 0 | — |
case-20 | fail→pass | 12,951 | 9,153 | -29% | 1 | 1 | 0% | 2,390 | 7,937 | +232% | 0 | 0 | — |
case-21 | fail→pass | 15,474 | 11,124 | -28% | 1 | 1 | 0% | 2,439 | 7,832 | +221% | 0 | 0 | — |
case-22 | pass→pass | 15,989 | 14,174 | -11% | 1 | 1 | 0% | 2,486 | 8,218 | +231% | 0 | 0 | — |
case-23 | pass→pass | 13,805 | 16,831 | +22% | 1 | 1 | 0% | 2,874 | 9,415 | +228% | 0 | 0 | — |
case-24 | pass→pass | 16,146 | 23,576 | +46% | 1 | 1 | 0% | 3,614 | 11,480 | +218% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 24 cases were attempted. The headline lift of +88 percentage points is the difference between those two pass rates over the 24 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.