chemoinformatics/scaffold-analysis/SKILL.md
Analyzes chemical libraries by scaffold using Bemis-Murcko scaffolds, generic frameworks, cyclic skeletons, matched molecular pair (MMP) analysis via mmpdb, R-group decomposition, Free-Wilson analysis, scaffold hopping, and chemotype-aware ML train/test splits. Use when identifying chemotype clusters in a library, deriving SAR transformation rules, decomposing series into R-groups, performing scaffold-balanced QSAR splits, or planning analog campaigns.
npx skillsauth add GPTomics/bioSkills bio-scaffold-analysisInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reference examples tested with: RDKit 2024.09+, mmpdb 3.1+, scikit-learn 1.4+, datamol 0.12+.
Before using code patterns, verify installed versions match. If versions differ:
pip show <package> then help(module.function) to check signaturesIf code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying.
Analyze chemical libraries by their underlying scaffolds. Bemis-Murcko (1996) is the canonical scaffold decomposition: ring systems + linkers, with all R-groups stripped. Generic framework + cyclic skeleton are progressively-more-abstract views. Scaffold analysis underpins QSAR train/test splits (preventing data leakage), library diversity assessment, chemotype clustering, R-group decomposition for SAR modeling, and matched molecular pair analysis (MMPA). The choice of scaffold representation determines whether two compounds are "the same series" -- a critical decision for medicinal chemistry workflows.
For reaction-based enumeration and Free-Wilson, see chemoinformatics/reaction-enumeration. For scaffold-hopping via fingerprints, see chemoinformatics/similarity-searching. For 3D shape-based scaffold hopping, see chemoinformatics/shape-similarity.
| Representation | Origin | Definition | Use case | Fails when | |----------------|--------|------------|----------|------------| | Bemis-Murcko scaffold | Bemis & Murcko 1996 | Ring systems + linkers, R-groups stripped | Default chemotype identifier | Linear molecules (no rings) -> empty scaffold | | Generic framework | Bemis & Murcko 1996 | Bemis-Murcko with all atoms set to C, all bonds single | Topology comparison | Loses heteroatom info | | Cyclic skeleton (CSK) | Custom RDKit transformation | Ring atoms only, all C, all single | Pure ring-topology view | Loses linker info; not a built-in Murcko option | | Murcko atom indices | Derived by matching the scaffold to the parent | Parent-molecule atom indices | Programmatic operations | Symmetry can yield multiple equivalent matches |
from rdkit import Chem
from rdkit.Chem.Scaffolds import MurckoScaffold
def all_scaffold_views(smi):
mol = Chem.MolFromSmiles(smi)
bm = MurckoScaffold.GetScaffoldForMol(mol)
bm_smi = Chem.MolToSmiles(bm)
generic = MurckoScaffold.MakeScaffoldGeneric(bm)
generic_smi = Chem.MolToSmiles(generic)
return {
'bemis_murcko': bm_smi,
'generic_framework': generic_smi,
}
Example: Cc1ccc(C(=O)NCC2CCCC2)cc1 -> Bemis-Murcko c1ccc(C(=O)NCC2CCCC2)cc1; generic C1CCC(C(C)CCC2CCCC2)CC1 in current RDKit.
Goal: Group compounds by shared Bemis-Murcko scaffold.
Approach: Compute scaffold for each compound; group by scaffold SMILES.
from collections import defaultdict
def scaffold_clusters(smiles_list):
clusters = defaultdict(list)
for smi in smiles_list:
mol = Chem.MolFromSmiles(smi)
if mol is None:
continue
scaffold = MurckoScaffold.GetScaffoldForMol(mol)
scaffold_smi = Chem.MolToSmiles(scaffold)
clusters[scaffold_smi].append(smi)
return clusters
Output: dict {scaffold_smiles: [compound_smiles, ...]}. Cluster sizes inform library diversity.
For QSAR / ML, random train/test split causes data leakage: compounds from the same chemotype (analogs in same series) end up in both. Bemis-Murcko split puts entire scaffolds in train or test, never both.
from rdkit.Chem.Scaffolds import MurckoScaffold
def scaffold_split(df, smiles_col='smiles', train_frac=0.8, seed=42):
import random
rng = random.Random(seed)
scaffolds = defaultdict(list)
invalid_positions = []
for pos, smi in enumerate(df[smiles_col].tolist()):
mol = Chem.MolFromSmiles(smi)
if mol is None:
invalid_positions.append(pos)
continue
scaff = Chem.MolToSmiles(MurckoScaffold.GetScaffoldForMol(mol))
scaffolds[scaff].append(pos)
if invalid_positions:
raise ValueError(f'Invalid SMILES at row positions: {invalid_positions}')
scaffold_sets = list(scaffolds.values())
rng.shuffle(scaffold_sets)
scaffold_sets.sort(key=lambda x: len(x), reverse=True)
n_total = sum(len(s) for s in scaffold_sets)
n_train = int(n_total * train_frac)
if len(scaffold_sets) < 2:
raise ValueError('A scaffold split requires at least two scaffolds')
train_idx = list(scaffold_sets[0])
test_idx = []
for i, scaff_set in enumerate(scaffold_sets[1:], start=1):
if not test_idx and i == len(scaffold_sets) - 1:
test_idx.extend(scaff_set)
elif abs(len(train_idx) + len(scaff_set) - n_train) < abs(len(train_idx) - n_train):
train_idx.extend(scaff_set)
else:
test_idx.extend(scaff_set)
return df.iloc[train_idx], df.iloc[test_idx]
Effect on benchmark metrics: A scaffold split often produces different performance from a random split because it tests transfer across scaffold groups. The size and meaning of the gap are dataset- and deployment-dependent; it is not a direct universal measure of memorization.
Caveat: Bemis-Murcko split is one scaffold-split; for production ML, consider time split (newer compounds in test) or activity-cliff-balanced split.
Class-imbalanced datasets: Scaffold-only assignment can yield skewed class distributions. Chemprop's scaffold_balanced split balances scaffold-group sizes; it is not label-stratified. If both group isolation and label balance are required, use a validated group-aware stratification procedure such as StratifiedGroupKFold where its assumptions fit, then audit every fold for scaffold overlap and endpoint balance.
Goal: Given a defined scaffold and a set of analog compounds, extract the R-group at each numbered attachment point into a tabular SAR matrix.
from rdkit.Chem import rdRGroupDecomposition as rgd
def decompose_series(compounds, scaffold_smiles_with_R):
scaffold = Chem.MolFromSmiles(scaffold_smiles_with_R)
if scaffold is None:
raise ValueError('Invalid scaffold SMARTS/SMILES')
parsed = [(i, Chem.MolFromSmiles(s)) for i, s in enumerate(compounds)]
invalid = [i for i, mol in parsed if mol is None]
if invalid:
raise ValueError(f'Invalid compound SMILES at positions: {invalid}')
mols = [mol for _, mol in parsed]
decomp, unmatched = rgd.RGroupDecompose([scaffold], mols, asSmiles=True)
unmatched_set = set(unmatched)
matched_positions = [i for i in range(len(mols)) if i not in unmatched_set]
return decomp, matched_positions, list(unmatched)
scaffold = 'c1ccc(C(=O)N[*:1])cc1-[*:2]'
compounds = ['c1ccc(C(=O)NCC)cc1F', 'c1ccc(C(=O)NCCC)cc1Cl']
table = decompose_series(compounds, scaffold)
Output: list of {'Core': scaffold, 'R1': r1_smiles, 'R2': r2_smiles} dicts. Used for Free-Wilson analysis (see reaction-enumeration skill).
Goal: Mine a SAR dataset for substructure transformations and their associated activity changes.
Approach: Fragment all compounds into core + variable side; index pairs differing by one transformation; report delta(activity) per transformation.
mmpdb fragment data.smi -o data.fragments
mmpdb index data.fragments -o data.mmpdb
mmpdb transform --smiles 'COc1ccccc1' --property pIC50 data.mmpdb
Output: ranked transformations with delta(pIC50), N pairs, confidence.
Interpret transformation effects from pair count, chemical-context diversity, dependence among pairs, uncertainty intervals, and prospective validation. Do not convert a universal pair-count/effect-size table into reliability labels.
Classical MMPA: "Me -> F always +0.5 log units." Context-based MMPA: "Me -> F adjacent to amide is +0.5; Me -> F adjacent to ester is -0.1."
Matched-pair effects can depend strongly on the local chemical environment, so report the transformation together with its attachment-point context rather than treating a global mean as universal (Raut & Dixit 2025). Use mmpdb's stored environments or a custom stratified analysis to compare context-specific effects.
Goal: Find compounds with different scaffold but similar 3D shape / pharmacophore / activity.
| Method | Approach | Tools | |--------|----------|-------| | 2D similarity with FCFP4 | Functional-class fingerprint Tanimoto | similarity-searching skill | | 3D shape (ROCS) | Tanimoto on shape + color volumes | shape-similarity skill | | Pharmacophore | Common pharmacophore features | pharmacophore-modeling skill | | Maximum Common Substructure (MCS) | Largest shared substructure | similarity-searching skill (rdFMCS) | | Deep scaffold hopping | Conditional molecular generation | DeepHop (Zheng et al. 2021) |
For systematic scaffold-hop discovery, combine:
Goal: Identify "analog series" within a library -- compounds sharing a scaffold + co-varying R-groups.
def detect_series(smiles_list, min_size=3):
clusters = scaffold_clusters(smiles_list)
series = {scaff: cmpds for scaff, cmpds in clusters.items()
if len(cmpds) >= min_size}
return series
Series counts depend on library provenance, standardization, scaffold definition, and minimum size. Report the observed distribution and use series as one possible unit for SAR analysis.
Trigger: Compound has no rings (e.g., fatty acid, simple amine).
Mechanism: Bemis-Murcko strips R-groups; no rings = nothing remains.
Symptom: Scaffold is empty string; molecules cluster together as "no scaffold".
Fix: For linear-rich libraries, augment with linear chain length / functional group features.
Trigger: Compound has spiro or bridged ring system.
Mechanism: All ring atoms included; result is the entire ring system without R-groups.
Symptom: Apparently different drugs share a "scaffold" because of common spiro center.
Fix: Validate visually; use generic framework for topology-only comparison.
Trigger: Distinguishing pyridine vs benzene scaffolds.
Mechanism: MakeScaffoldGeneric sets all atoms to C.
Symptom: Pyridine and benzene scaffolds reported as identical.
Fix: Use Bemis-Murcko (heteroatoms preserved); generic framework for topology only.
Trigger: Library has many singletons + few large scaffolds.
Mechanism: Large scaffolds dominate; greedy assignment puts them in train.
Symptom: Test set is mostly singleton scaffolds; metrics misleading.
Fix: Use stratified scaffold split (balance test classes); or scaffold-balanced cross-validation.
Trigger: Transformation rare in dataset.
Mechanism: Need enough pairs to estimate delta(activity).
Symptom: Transformation reports N=2 with very large delta.
Fix: Report uncertainty and context diversity, avoid overinterpreting sparse transformations, and seek additional matched evidence where appropriate.
Trigger: Multiple positions in scaffold could match same R-group.
Mechanism: Multiple core embeddings, symmetry, and unlabeled attachment choices can yield assignments that differ from the medicinal-chemistry convention.
Symptom: R1/R2 columns mixed up.
Fix: Specify labeled attachment points, inspect the returned rows and unmatched indices, and use RGroupDecompositionParameters for the intended matching/alignment behavior.
| Concept | Definition A | Definition B | Pick which |
|---------|--------------|--------------|------------|
| Bemis-Murcko scaffold | Atoms in rings + linkers | Same | RDKit default |
| Generic framework | All C, all single bonds | All C, original bonds | MakeScaffoldGeneric implements the first; preserve bond orders with an explicit custom transformation |
| Cyclic skeleton | Only ring atoms | Only ring atoms, generic | Implement explicitly; it is not an RDKit Murcko flag |
| "Series" | Same Bemis-Murcko | Tanimoto > 0.8 + same MW | Bemis-Murcko for SAR; Tanimoto for screening |
For ML splits: Bemis-Murcko. For library diversity: Bemis-Murcko + cluster size. For series detection: Bemis-Murcko + R-group decomposition.
| Symptom | Cause | Fix |
|---------|-------|-----|
| Murcko scaffold includes unexpected linker atoms | Bemis-Murcko linkers connect ring systems by definition | Inspect the definition; for hierarchical networks use rdScaffoldNetwork.ScaffoldNetworkParams with CreateScaffoldNetwork |
| Singleton scaffolds dominate library | Aggressive standardization | Check for tautomer-induced scaffold variation; canonicalize first |
| R-group decomposition empty | Mol doesn't match scaffold | Use FMCS to find actual shared core |
| mmpdb missing transformations | Cores too restrictive | Try smaller core requirement |
| Scaffold split gives all to train | Few scaffolds; large clusters | Add singleton-spread strategy; use Murcko-and-Linker variant |
| Generic framework same for different drugs | Stripped heteroatom info | Use Bemis-Murcko (preserves heteroatoms) |
| MakeScaffoldGeneric error | RDKit version issue | RDKit 2024.09+ uses Chem.Scaffolds.MurckoScaffold |
tools
End-to-end CLIP-seq pipeline from FASTQ to ENCODE-compliant binding sites, single-nucleotide crosslink maps, annotation, motifs, and (optionally) differential binding. Use when running the full Yeo lab eCLIP / iCLIP / iCLIP2 / iCLIP3 / irCLIP / PAR-CLIP analysis with SMInput control, protocol-specific UMI extraction, ENCODE STAR parameters, CLIPper or Skipper peak calling with stringent log2 FC and -log10 p thresholds, IDR rescue and self-consistency QC, and downstream motif registration with mCross or PEKA.
development
Detect, date, and contextualize whole-genome duplication (WGD / paleopolyploidy) events using wgd v2 (Chen et al 2024), KsRates (Sensalari 2022 substitution-rate-corrected Ks dating), DupGen_finder (Qiao 2019), MAPS (Li 2018 phylogenomic), POInT (Conant 2008 ordered-block), SLEDGe (2024 ML-based), Whale.jl (Bayesian DL+WGD), and synteny-anchored paranome construction. Use when identifying ancient polyploidy from Ks distributions and synteny block analysis, positioning WGD events relative to speciation, distinguishing tandem from segmental from WGD duplications, dating the 2R/3R vertebrate / fish / salmonid WGDs, building paranome and Ks-age mixture models, applying KsRates substitution-rate correction across lineages, or testing alternative biased-fractionation / dosage-balance models post-WGD.
tools
Build whole-genome alignments using Progressive Cactus (Armstrong 2020 reference-free clade-level WGA), Minigraph-Cactus (Hickey 2024 pangenome-aware), LASTZ chain/net (UCSC pipeline), MUMmer4 (Marçais 2018 pairwise), minimap2 -x asm5/10/20 (Li 2018 fast pairwise), AnchorWave (Song 2022 WGD-aware), and Mauve / progressiveMauve (bacterial). Operates the HAL toolkit (Hickey 2013) for downstream extraction including halSynteny, halLiftover, halBranchMutations, and hal2maf. Use when constructing multi-species alignments for comparative-annotation projection (TOGA), synteny detection, conservation analyses (phyloP / PhastCons), or pangenome graph construction; selecting between reference-free (Cactus) and reference-anchored (LASTZ chains/nets) approaches; tuning sensitivity for closely vs distantly related genomes; or producing HAL files for genome-wide downstream tools.
development
Detect syntenic blocks and structural rearrangements between genomes using MCScanX (Wang 2012), JCVI/MCScan (Tang 2008 Python), GENESPACE (Lovell 2022) for orthology-anchored riparian visualization, SyRI for structural variation, AnchorWave for sequence-level synteny, i-ADHoRe 3.0 for highly diverged species, SynNet for synteny networks, and ntSynt for multi-genome macrosynteny. Use when identifying collinear gene blocks across species, distinguishing macrosynteny from microsynteny, detecting inversions/translocations/duplications, anchoring orthology in WGD lineages, producing publication riparian plots, computing synteny block age via Ks (cross-references whole-genome-duplication), or running synteny-aware ortholog inference in polyploids.