# Bio Applied Rdkit Basics

> Parse SMILES with RDKit, compute MW/LogP/TPSA/HBD/HBA descriptors and Lipinski Ro5, build Morgan/ECFP4 and MACCS fingerprints, score Tanimoto similarity. Use for cheminformatics, drug-likeness screening, or fingerprint similarity search.

- Skill: `pavel-kravchenko/bio-applied-rdkit-basics` (Agent Skill)
- Install (CLI): `npx skillmds@latest add pavel-kravchenko/bio-applied-rdkit-basics`
- Raw SKILL.md: https://api.skillmd.com/api/skills/pavel-kravchenko/bio-applied-rdkit-basics/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: pavel-kravchenko (https://skillmd.com/u/pavel-kravchenko)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/pavel-kravchenko/bio-applied-rdkit-basics

---


# Molecular Representations with RDKit

## When to Use
- Parsing, validating, or canonicalizing SMILES/InChI strings for a set of compounds
- Computing molecular descriptors (molecular weight, LogP, TPSA, HBD/HBA) for drug-likeness triage
- Checking Lipinski's Rule of Five for oral bioavailability screening
- Generating Morgan (ECFP4) or MACCS fingerprints for ML featurization or similarity search
- Computing pairwise Tanimoto similarity to find near-neighbors in a small compound library

## Version Compatibility
- rdkit ≥ 2023.09 (pip package `rdkit`, no separate `rdkit-pypi` needed since 2022)
- Python ≥ 3.9
- pandas ≥ 1.5, seaborn ≥ 0.12, matplotlib ≥ 3.6 (for the similarity heatmap)

## Prerequisites
- `pip install rdkit pandas seaborn matplotlib`
- Basic familiarity with SMILES notation and organic chemistry (functional groups, aromaticity)

**Goal:** Parse a set of SMILES strings into RDKit molecule objects and validate them.

**Approach:** `Chem.MolFromSmiles` returns `None` on invalid input instead of raising — always check for `None` before downstream use.

```python
from rdkit import Chem

def parse_smiles(smiles_dict: dict[str, str]) -> dict[str, Chem.Mol]:
    """Parse a {name: SMILES} dict into RDKit Mol objects, dropping invalid entries.

    RDKit's MolFromSmiles returns None (not an exception) for malformed SMILES,
    so unchecked use silently propagates None into later steps and crashes there.
    """
    mols = {}
    for name, smi in smiles_dict.items():
        mol = Chem.MolFromSmiles(smi)
        if mol is None:
            print(f"{name}: INVALID SMILES -> {smi!r}")
            continue
        mols[name] = mol
    return mols

smiles_dict = {
    "Aspirin":   "CC(=O)Oc1ccccc1C(=O)O",
    "Caffeine":  "Cn1cnc2c1c(=O)n(C)c(=O)n2C",
    "Ibuprofen": "CC(C)Cc1ccc(cc1)C(C)C(=O)O",
}
mols = parse_smiles(smiles_dict)
```

**Goal:** Draw a 2D grid image of the parsed molecules for visual inspection.

**Approach:** `Draw.MolsToGridImage` lays out molecules with per-molecule legends; useful in Jupyter or saved to disk with `.save()`.

```python
from typing import Optional
from rdkit.Chem import Draw

def draw_grid(mols: dict[str, Chem.Mol], mols_per_row: int = 3, path: Optional[str] = None):
    """Render molecules as a 2D grid image; saves to `path` if given, else returns the image."""
    img = Draw.MolsToGridImage(
        list(mols.values()),
        legends=list(mols.keys()),
        molsPerRow=mols_per_row,
        subImgSize=(300, 250),
    )
    if path:
        img.save(path)
    return img

img = draw_grid(mols, path="/tmp/molecule_grid.png")
```

**Goal:** Compute drug-likeness descriptors and apply Lipinski's Rule of Five.

**Approach:** Pull MW/LogP/TPSA from `Descriptors` and HBD/HBA counts from `rdMolDescriptors`, then flag compounds violating Ro5 thresholds (MW≤500, LogP≤5, HBD≤5, HBA≤10).

```python
import pandas as pd
from rdkit.Chem import Descriptors, rdMolDescriptors

def compute_descriptors(mols: dict[str, Chem.Mol]) -> pd.DataFrame:
    """Compute MW, LogP, HBD, HBA, TPSA and flag Lipinski Rule of Five pass/fail."""
    rows = []
    for name, mol in mols.items():
        rows.append({
            "Name": name,
            "MW":   Descriptors.MolWt(mol),
            "LogP": Descriptors.MolLogP(mol),
            "HBD":  rdMolDescriptors.CalcNumHBD(mol),
            "HBA":  rdMolDescriptors.CalcNumHBA(mol),
            "TPSA": Descriptors.TPSA(mol),
        })
    desc_df = pd.DataFrame(rows)
    desc_df["Ro5_pass"] = (
        (desc_df["MW"] <= 500) & (desc_df["LogP"] <= 5) &
        (desc_df["HBD"] <= 5) & (desc_df["HBA"] <= 10)
    )
    return desc_df

desc_df = compute_descriptors(mols)
print(desc_df)
```

**Goal:** Generate Morgan (ECFP4) and MACCS fingerprints for similarity/ML use.

**Approach:** `radius=2` with `nBits=2048` reproduces the standard ECFP4 fingerprint; MACCS keys are a fixed 167-bit structural-key fingerprint requiring no parameters.

```python
from rdkit.Chem import AllChem
from rdkit.Chem.MACCSkeys import GenMACCSKeys

def compute_fingerprints(mols: dict[str, Chem.Mol], radius: int = 2, n_bits: int = 2048):
    """Return (morgan_fps, maccs_fps) dicts keyed by molecule name.

    radius=2, n_bits=2048 is the conventional "ECFP4" configuration.
    """
    morgan_fps = {
        name: AllChem.GetMorganFingerprintAsBitVect(mol, radius=radius, nBits=n_bits)
        for name, mol in mols.items()
    }
    maccs_fps = {name: GenMACCSKeys(mol) for name, mol in mols.items()}
    return morgan_fps, maccs_fps

morgan_fps, maccs_fps = compute_fingerprints(mols)
```

**Goal:** Compute a pairwise Tanimoto similarity matrix and visualize it as a heatmap.

**Approach:** `DataStructs.TanimotoSimilarity` compares two bit vectors; loop over all name pairs and plot with seaborn.

```python
import seaborn as sns
import matplotlib.pyplot as plt
from rdkit import DataStructs

def tanimoto_matrix(fps: dict[str, "DataStructs.ExplicitBitVect"]) -> pd.DataFrame:
    """Build an all-pairs Tanimoto similarity matrix from a {name: fingerprint} dict."""
    names = list(fps.keys())
    sim_matrix = pd.DataFrame(index=names, columns=names, dtype=float)
    for n1 in names:
        for n2 in names:
            sim_matrix.loc[n1, n2] = DataStructs.TanimotoSimilarity(fps[n1], fps[n2])
    return sim_matrix

sim_matrix = tanimoto_matrix(morgan_fps)
sns.heatmap(sim_matrix.astype(float), annot=True, cmap="Blues", vmin=0, vmax=1)
plt.title("Tanimoto Similarity (ECFP4)")
plt.show()
```

## Pitfalls
- **SMILES canonicalization**: Different SMILES strings can represent the same molecule; canonicalize with `Chem.MolToSmiles(mol)` before string-based deduplication or comparison
- **Silent parse failures**: `MolFromSmiles` returns `None` instead of raising — always check for `None` before calling any descriptor/fingerprint function on the result
- **Stereochemistry**: Default Morgan/MACCS fingerprints from 2D SMILES can merge enantiomers with different bioactivity; use `useChirality=True` in `GetMorganFingerprintAsBitVect` when stereochemistry matters
- **Descriptor scaling**: MW, LogP, and TPSA live on very different numeric scales; standardize (z-score) before feeding descriptors into ML models
- **Fingerprint type mismatch**: Morgan and MACCS fingerprints are not comparable to each other — compute Tanimoto similarity only between fingerprints of the same type/parameters
- **API deprecation**: `AllChem.GetMorganFingerprintAsBitVect` still works but is deprecated in newer RDKit releases in favor of `rdFingerprintGenerator.GetMorganGenerator(radius, fpSize).GetFingerprint(mol)` — prefer the generator API in new code

## See Also
- `cheminformatics-drug-discovery` — end-to-end RDKit + QSAR + docking + ADMET workflow
- `bio-applied-virtual-screening` — docking ligand libraries and ranking hits by ADMET/QSAR
- `bio-applied-molecular-gnn` — SMILES-to-graph GNN property prediction beyond fingerprint/RF
- `bio-applied-molecular-modeling` — force-field energetics and structure QC for docked/modeled molecules

