Skill Tracer
Skills › Scientific Agent Skills › molfeat › choosing_a_featurizer.md

choosing_a_featurizer.md

101 lines · skills/molfeat/references/choosing_a_featurizer.md at 5f2bc03

Open on GitHub
1# Choosing representations and evaluating molecular models
2
3Start with ECFP and a simple estimator. Choose descriptors for interpretability,
4pharmacophores for a stated interaction hypothesis, and 3D features only when the
5conformer protocol is justified. Pretrained embeddings are candidates to evaluate;
6model size or pretraining volume does not establish task performance.
7
8Use identical splits, assay endpoints, and molecule policies across representations.
9Keep close analogues, duplicate structures, and repeated measurements together when
10they would otherwise leak information. A scaffold split is useful for testing novel
11cores; temporal splitting may better approximate future screening. Record which
12scientific generalization question the split addresses.

Choosing representations and evaluating molecular models

Start with ECFP and a simple estimator. Choose descriptors for interpretability, pharmacophores for a stated interaction hypothesis, and 3D features only when the conformer protocol is justified. Pretrained embeddings are candidates to evaluate; model size or pretraining volume does not establish task performance.

Use identical splits, assay endpoints, and molecule policies across representations. Keep close analogues, duplicate structures, and repeated measurements together when they would otherwise leak information. A scaffold split is useful for testing novel cores; temporal splitting may better approximate future screening. Record which scientific generalization question the split addresses.

Raw lines 1 to 13
14## Toy grouped QSAR pipeline
16Executed on synthetic data to check API behavior only; its score has no scientific
17meaning. Replace inputs, labels, and group assignments with curated assay records.
18All preprocessing that learns from data stays inside the cross-validation pipeline.
20```python
21import numpy as np
22from sklearn.pipeline import Pipeline
23from sklearn.preprocessing import StandardScaler
24from sklearn.impute import SimpleImputer
25from sklearn.linear_model import Ridge
26from sklearn.model_selection import GroupKFold, cross_val_score
27from molfeat.calc import RDKitDescriptors2D
28from molfeat.trans import MoleculeTransformer
30smiles = ["CCO", "CCCO", "CCCCO", "CCCCCO",
31 "CCN", "CCCN", "CCCCN", "CCCCCN"]
32y = np.arange(8, dtype=float) # artificial labels
33groups = np.array([0, 0, 1, 1, 2, 2, 3, 3]) # artificial groups
34pipeline = Pipeline([
35 ("features", MoleculeTransformer(RDKitDescriptors2D(), dtype=np.float64)),
36 ("impute", SimpleImputer(strategy="median")),
37 ("scale", StandardScaler()),
38 ("regress", Ridge(alpha=1.0)),
39])
40scores = cross_val_score(
41 pipeline, smiles, y, groups=groups, cv=GroupKFold(4),
42 scoring="neg_mean_absolute_error", error_score="raise",
44assert np.isfinite(scores).all()
45```
47Validate molecular inputs before splitting while preserving labels/IDs. Do not drop
48rows inside a scikit-learn transformer without also handling the target vector.
49`MoleculeTransformer.fit` can remove training columns containing NaN; keep feature
50names after fitting. Imputers do not automatically handle Inf, and missing validation
51descriptors still need a declared policy. Tune representations/hyperparameters in an
52inner split and reserve an untouched evaluation set.
54Classification requires class-aware metrics and sufficient positive/negative examples
55in each fold. ROC AUC alone can obscure poor precision in sparse screening libraries;
56report PR metrics and enrichment at a prespecified screening budget when applicable.
57Confirm activity units, assay comparability, censoring, and target transforms before
58regression; raw IC50 values are not interchangeable with pIC50.

Toy grouped QSAR pipeline

Executed on synthetic data to check API behavior only; its score has no scientific meaning. Replace inputs, labels, and group assignments with curated assay records. All preprocessing that learns from data stays inside the cross-validation pipeline.

import numpy as np
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.impute import SimpleImputer
from sklearn.linear_model import Ridge
from sklearn.model_selection import GroupKFold, cross_val_score
from molfeat.calc import RDKitDescriptors2D
from molfeat.trans import MoleculeTransformer

smiles = ["CCO", "CCCO", "CCCCO", "CCCCCO",
          "CCN", "CCCN", "CCCCN", "CCCCCN"]
y = np.arange(8, dtype=float)  # artificial labels
groups = np.array([0, 0, 1, 1, 2, 2, 3, 3])  # artificial groups
pipeline = Pipeline([
    ("features", MoleculeTransformer(RDKitDescriptors2D(), dtype=np.float64)),
    ("impute", SimpleImputer(strategy="median")),
    ("scale", StandardScaler()),
    ("regress", Ridge(alpha=1.0)),
])
scores = cross_val_score(
    pipeline, smiles, y, groups=groups, cv=GroupKFold(4),
    scoring="neg_mean_absolute_error", error_score="raise",
)
assert np.isfinite(scores).all()

Validate molecular inputs before splitting while preserving labels/IDs. Do not drop rows inside a scikit-learn transformer without also handling the target vector. MoleculeTransformer.fit can remove training columns containing NaN; keep feature names after fitting. Imputers do not automatically handle Inf, and missing validation descriptors still need a declared policy. Tune representations/hyperparameters in an inner split and reserve an untouched evaluation set.

Classification requires class-aware metrics and sufficient positive/negative examples in each fold. ROC AUC alone can obscure poor precision in sparse screening libraries; report PR metrics and enrichment at a prespecified screening budget when applicable. Confirm activity units, assay comparability, censoring, and target transforms before regression; raw IC50 values are not interchangeable with pIC50.

Raw lines 14 to 59
60## Bounded-memory virtual screening
62Illustrative: `classifier` is fitted and its training featurizer is supplied. Invalid
63inputs must be reported, and output rows carry their original positions. This example
64retains only a chunk's feature matrix and immediately emits scores.
66```python
67import numpy as np
69def scored_chunks(smiles, transformer, classifier, active_label=1, chunk_size=1000):
70 if chunk_size <= 0:
71 raise ValueError("chunk_size must be positive")
72 matches = np.flatnonzero(np.asarray(classifier.classes_) == active_label)
73 if len(matches) != 1:
74 raise ValueError("active_label must identify one fitted classifier class")
75 column = int(matches[0])
76 for start in range(0, len(smiles), chunk_size):
77 chunk = smiles[start:start + chunk_size]
78 X, valid_ids = transformer(chunk, ignore_errors=True)
79 rejected = sorted(set(range(len(chunk))) - set(valid_ids))
80 original_positions = np.asarray(valid_ids, dtype=int) + start
81 if len(valid_ids):
82 if not np.isfinite(X).all():
83 raise ValueError("nonfinite screening features require an explicit policy")
84 probabilities = classifier.predict_proba(X)[:, column]
85 else:
86 probabilities = np.empty(0)
87 yield original_positions, probabilities, [start + i for i in rejected]
88```
90Write results per chunk, or maintain a bounded top-k heap. Avoid featurizing a million
91molecules at once or accumulating every chunk before `vstack`. A probability from an
92uncalibrated classifier is a ranking score, not established biological confidence.
93Check chemical applicability and validate prioritized compounds experimentally.

Bounded-memory virtual screening

Illustrative: classifier is fitted and its training featurizer is supplied. Invalid inputs must be reported, and output rows carry their original positions. This example retains only a chunk's feature matrix and immediately emits scores.

import numpy as np

def scored_chunks(smiles, transformer, classifier, active_label=1, chunk_size=1000):
    if chunk_size <= 0:
        raise ValueError("chunk_size must be positive")
    matches = np.flatnonzero(np.asarray(classifier.classes_) == active_label)
    if len(matches) != 1:
        raise ValueError("active_label must identify one fitted classifier class")
    column = int(matches[0])
    for start in range(0, len(smiles), chunk_size):
        chunk = smiles[start:start + chunk_size]
        X, valid_ids = transformer(chunk, ignore_errors=True)
        rejected = sorted(set(range(len(chunk))) - set(valid_ids))
        original_positions = np.asarray(valid_ids, dtype=int) + start
        if len(valid_ids):
            if not np.isfinite(X).all():
                raise ValueError("nonfinite screening features require an explicit policy")
            probabilities = classifier.predict_proba(X)[:, column]
        else:
            probabilities = np.empty(0)
        yield original_positions, probabilities, [start + i for i in rejected]

Write results per chunk, or maintain a bounded top-k heap. Avoid featurizing a million molecules at once or accumulating every chunk before vstack. A probability from an uncalibrated classifier is a ranking score, not established biological confidence. Check chemical applicability and validate prioritized compounds experimentally.

Raw lines 60 to 94
95## Compare featurizers without mixing interfaces
97Use `MoleculeTransformer(FPCalculator(...))` for a calculator, `FeatConcat` directly
98for an `FPVecTransformer` container, and `PretrainedHFTransformer` directly for
99embeddings. Wrapping either of the latter as a single-molecule calculator changes the
100expected batch interface. Fix seeds and use paired evaluation on the same folds.
101See [API contracts](api_reference.md) and [examples](examples.md) for executable patterns.

Compare featurizers without mixing interfaces

Use MoleculeTransformer(FPCalculator(...)) for a calculator, FeatConcat directly for an FPVecTransformer container, and PretrainedHFTransformer directly for embeddings. Wrapping either of the latter as a single-molecule calculator changes the expected batch interface. Fix seeds and use paired evaluation on the same folds. See API contracts and examples for executable patterns.

Raw lines 95 to 101