Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Python Cramer Classifier

License: GPLv2+

Powered by RDKit

A Python implementation of the Cramer decision tree for toxicological hazard classification of chemical substances. This tool classifies chemicals into one of three risk classes (I-III) based on structural and chemical properties relevant to systemic toxicity assessment.

Original Reference: Cramer, G. M., Ford, R. A., & Hall, R. L. (1978). Estimation of toxic hazard—a decision tree approach. Food and Cosmetics Toxicology, 16(3), 255-276.

Implementation derived from: Toxtree (https://toxtree.sourceforge.net/), licensed under the GNU General Public License v2.0 or later (GPLv2+)

Features

  • Full decision tree implementation: All 39 rules (33 main + 6 auxiliary) from the original Cramer tree
  • RDKit-based: Uses RDKit for molecular analysis and SMARTS pattern matching
  • Automatic hydrogen handling: Explicit hydrogens added automatically—users don't need to manage this
  • Salt fragmentation: Automatically extracts and analyzes organic residues from salt compounds
  • Transparent reasoning: Full trace of decision steps for interpretability
  • InChI lookups: Q1 (body constituents) and Q22 (food components) use exact InChI matching against reference databases

Performance

Validated against the Munro dataset:

Dataset Compounds Accuracy Context
Munro (Expert Baseline) 613 88.91% Expert-curated toxicity reference. Matches Toxtree baseline.

Performance Analysis

  • Munro validation: 88.91% agreement with Toxtree confirms correct implementation of the decision tree logic.

Installation

The package requires RDKit:

uv add cdt

Usage

from rdkit import Chem
from cdt.cdt.decision_tree import classify

# Create molecule from SMILES (explicit hydrogens added automatically)
mol = Chem.MolFromSmiles("CCO")  # Ethanol

# Classify
result = classify(mol)

print(f"Cramer Class: {result.cramer_class}")        # 1, 2, or 3
print(f"Category: {result.category_name}")            # "Low", "Intermediate", "High"
print(f"Description: {result.category_description}")

# View decision trace
for entry in result.trace:
    print(f"Q{entry.question_id}: {entry.answer}")

Note: Explicit hydrogens are automatically added during classification. Users do not need to call Chem.AddHs() manually.

Output

CramerResult

@dataclass
class CramerResult:
    cramer_class: int                      # 1 (Low), 2 (Intermediate), or 3 (High)
    category_name: str                     # "Low (Class I)", "Intermediate (Class II)", etc.
    category_description: str              # Full text description
    trace: list[TraceEntry]               # Decision steps taken
    notes: list[str]                       # Warnings about stubs/simplifications

TraceEntry

@dataclass
class TraceEntry:
    question_id: str                       # "Q1", "Q2", etc.
    question_text: str                     # Human-readable question
    answer: bool                           # True or False

Implementation Details

Module Structure

  • __init__.py: Public API (main classify() function)
  • mol_flags.py: Preprocessing - molecular feature extraction (MolFlags, analyse())
  • functional_groups.py: SMARTS pattern catalogue and group detection helpers
  • rules.py: All 39 decision rule functions
  • decision_tree.py: Transition matrix and tree execution engine
  • lookup.py: InChI lookup functionality
  • result.py: Result and trace data structures

Decision Layers

  1. Layer 1 - Preprocessing (mol_flags.py): Extract boolean flags and ring information
  2. Layer 2 - Functional Groups (functional_groups.py): SMARTS pattern matching
  3. Layer 3 - Lookups (lookup.py): InChI database queries
  4. Layer 4 - Rules (rules.py): 39 decision functions
  5. Layer 5 - Engine (decision_tree.py): Transition matrix and execution

Rules Overview

Simple Rules (Direct checks)

  • Q3: Elemental composition (C, H, O, N, S(II))
  • Q7: Heterocyclic ring
  • Q10: 3-membered heterocyclic ring
  • Q12: Heteroaromatic ring
  • Q13: Ring with non-ring substituent
  • Q14: Multiple aromatic rings
  • Q19: Open-chain (acyclic)
  • Q23: Aromatic ring
  • Q27: Ring with non-ring substituent (same as Q13)
  • Q28: Multiple aromatic rings (same as Q14)

Functional Group Rules

  • Q2: Restricted N/S groups (nitro, cyano, N-nitroso, diazo, triazeno, quaternary N)
  • Q5: Simple aliphatic hydrocarbon or carbohydrate
  • Q6: Simple benzene ring with limited substituents
  • Q8: Lactone ring
  • Q16: Isoprene unit with simple groups (OH, CHO, COOH)
  • Q18: Unsaturated aldehyde (acrolein, methacrolein, etc.)
  • Q20: Functional group count limits
  • Q21: Multiple (>3) distinct functional groups
  • Q24: Single 5+ membered alicyclic ring with allowed substituents
  • Q25: Single 3-4 membered alicyclic ring with allowed substituents
  • Q26: Atoms covered by allowed groups (1-2 rings)
  • Q30: Aromatic substituents with chain length ≤5
  • Q32: Atoms covered by Q30 allowed group list

Stub Rules (Simplified)

  • Q4: Salt/sulphonate detection
  • Q9: Unstable lactone (simplified)
  • Q11: Allowed heterocyclic substituents
  • Q15: Hydrolysable groups (ester/acetal/lactone)
  • Q17: Terpene ester
  • Q29: Aromatic ring with hydrolysable ester
  • Q31: Acetal/ester residue (simplified)
  • Q33: Sulphonate/sulphamate with fragmentation limits

Auxiliary Rules (Q34-Q39)

Reuse logic from main rules (Q7, Q12, Q16, Q15, Q17, Q30)

Known Limitations

The implementation includes several simplifications documented with notes in the output:

Rule Limitation
Q5 "Simply branched" not verified - all non-acetylenic aliphatics pass
Q9 Lactone stability not simulated - returns conservative result
Q15 Hydrolysis not simulated - presence of groups taken as proxy
Q17 Terpene ester check simplified
Q20 Group count limits not fully enforced
Q24/Q25 Substituent types not completely verified
Q26 Atom coverage not fully verified
Q30 Substituent chain length not fully verified
Q31 Hydrolysis products not simulated
Q33 Metabolic fragmentation not simulated

These match the limitations in the original Java implementation or are acceptable simplifications for the Python port.

Testing

Three test files are included:

# Basic functionality test
python test_phase1.py

# Comprehensive validation (10 compounds)
python test_validation.py

# Unit tests for individual rules
python test_rules.py

Test Results

  • ✅ 10/10 validation tests pass
  • ✅ 7/7 unit tests pass
  • ✅ 100% rule implementation coverage
  • ✅ All stub rules properly documented

Data Files

  • data/bodymol.inchi (51 KB): InChI strings for body constituents (Q1)
  • data/foodmol.inchi (9.1 KB): InChI strings for food components (Q22)

Copied from ToxTree-core resources.

Licensing

This implementation is licensed under GPLv2, or (at your option) any later version, to align with the Toxtree source implementation, which is also licensed under these terms. Attribution: This code implements the Cramer decision tree algorithm from Toxtree (https://toxtree.sourceforge.net/), originally developed by Ideaconsult Ltd. and licensed under GPLv2. See LICENSE file for full GPLv2 text.

Key Implementation Details

Salt Handling (Q4)

Multi-fragment molecules (salts) are fragmented using fragment_salt(). The largest organic residue is extracted and analyzed through the full tree, while inorganic counterions are discarded. This allows organic compounds that are present as salts to be correctly classified based on their organic structure.

Carbohydrate Detection (Q5)

Distinguishes between:

  • Simple aliphatic hydrocarbons (C, H only)
  • Cyclic carbohydrates (C, H, O with oxygen in ring)
  • Acyclic carbohydrates (C, H, O with multiple alcohols + single aldehyde/ketone)

Ring oxygen in carbohydrates is treated as structural, not a functional group.

Hemiacetal vs Ether

Glucose correctly returns heterocyclic=False because the ring oxygen is structural, not an ether functional group.

Benzene Substituents (Q6)

Per-substituent validation ensures only OH, OCH3, OC2H5 substituents are allowed on benzene rings.

References

  • Cramer et al. (1978): Estimation of toxic hazard—a decision tree approach. Food and Cosmetics Toxicology, 16(3), 255-276.
  • Roberts et al. (2015): A practical guidance for Cramer class determination. Regulatory Toxicology and Pharmacology, 81, S2-S5.
  • Toxtree: https://toxtree.sourceforge.net/
  • RDKit: https://www.rdkit.org/

Publication

This implementation is described in: Patlewicz G, Mihalchik AL. Revisiting the Threshold of Toxicological Concern: a computational framework for structural classification, cross-scheme comparison and opportunities for refinement. Computational Toxicology 39: 100443 https://doi.org/10.1016/j.comtox.2026.100443

Version

0.1.0 - Python implementation of Cramer decision tree (validated against Toxtree 88.91% on Munro dataset)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages