Working with Tasks¶
This tutorial covers THEMAP's unified task system that integrates molecular data, protein data, and other metadata for comprehensive analysis.
Note
The code on this page is written against the current THEMAP API. The examples are illustrative — running them requires a real dataset directory laid out as shown below.
Understanding the Task System¶
The Task system in THEMAP provides a unified interface for working with multi-modal bioactivity prediction data:
- Tasks: individual prediction problems (e.g.
CHEMBL1023359) - Data modalities: molecules (
molecule_dataset) and metadata such as protein embeddings (metadata_datasets) - Folds: train / validation / test divisions, addressed with the
DataFoldenum - Caching: efficient feature storage and retrieval
Loading Tasks¶
Basic Task Loading¶
from themap.data.tasks import Tasks
# Load tasks from a directory structure
tasks = Tasks.from_directory(
directory="datasets/",
task_list_file="datasets/sample_tasks_list.json",
load_molecules=True,
load_proteins=True,
load_metadata=True,
cache_dir="cache/",
)
print(f"Loaded {len(tasks)} tasks")
Task Configuration File¶
The sample_tasks_list.json file defines which tasks belong to which fold:
{
"train": ["CHEMBL1023359", "CHEMBL1613776", "CHEMBL4078627"],
"test": ["CHEMBL2219358", "CHEMBL1963831"],
"valid": ["CHEMBL2219236"]
}
Directory Structure¶
datasets/
├── train/
│ ├── CHEMBL1023359.jsonl.gz # Molecular data
│ ├── CHEMBL1023359.fasta # Protein sequence
│ └── ...
├── test/
│ └── ...
├── valid/
│ └── ...
└── sample_tasks_list.json
Exploring Tasks¶
Fold-aware methods take a DataFold value (TRAIN, VALIDATION, or TEST), not
a string. There is no single "all folds" accessor, so concatenate the three folds
when you need every task ID.
from themap.data.enums import DataFold
# Per-fold counts
print(f"Total tasks: {len(tasks)}")
print(f"Train tasks: {tasks.get_num_fold_tasks(DataFold.TRAIN)}")
print(f"Test tasks: {tasks.get_num_fold_tasks(DataFold.TEST)}")
print(f"Valid tasks: {tasks.get_num_fold_tasks(DataFold.VALIDATION)}")
# Task IDs for a single fold
train_task_ids = tasks.get_task_ids(DataFold.TRAIN)
print(f"Training task IDs: {train_task_ids}")
def all_task_ids(tasks):
"""Return task IDs across every fold."""
return (
tasks.get_task_ids(DataFold.TRAIN)
+ tasks.get_task_ids(DataFold.VALIDATION)
+ tasks.get_task_ids(DataFold.TEST)
)
# Access an individual task
task = tasks.get_task_by_id("CHEMBL1023359")
print(f"Task {task.task_id} has {len(task.molecule_dataset)} molecules")
Data Modality Access¶
A Task exposes its molecules through molecule_dataset and any other modality
(such as protein embeddings) through the metadata_datasets dictionary, where
protein data lives under the "protein" key.
for task_id in all_task_ids(tasks)[:3]: # first 3 tasks
task = tasks.get_task_by_id(task_id)
print(f"\nTask: {task_id}")
# Molecular data
if task.molecule_dataset is not None:
print(f" Molecules: {len(task.molecule_dataset)}")
print(f" Sample SMILES: {task.molecule_dataset.smiles[0]}")
# Metadata modalities (e.g. protein embeddings)
if task.metadata_datasets:
print(f" Metadata modalities: {list(task.metadata_datasets.keys())}")
if "protein" in task.metadata_datasets:
print(" Has protein metadata")
Task-Based Distance Computation¶
Unified Distance Calculation¶
TaskDistanceCalculator orchestrates molecule, protein, and combined distances.
compute_all_distances returns a dictionary keyed by "molecules", "protein",
and "combined"; each value is a nested {target_id: {source_id: distance}} map.
from themap.distance import TaskDistanceCalculator
# Create the calculator (dataset_method drives molecule distances,
# metadata_method drives protein/metadata distances)
task_distance = TaskDistanceCalculator(
tasks=tasks,
dataset_method="cosine",
metadata_method="euclidean",
)
# Compute every distance type at once
all_distances = task_distance.compute_all_distances(
molecule_featurizer="ecfp",
combination="weighted_average",
weights={"molecules": 0.7, "protein": 0.3},
)
print("Distance types computed:")
print(f" Molecule distances: {len(all_distances['molecules'])} target tasks")
print(f" Protein distances: {len(all_distances['protein'])} target tasks")
print(f" Combined distances: {len(all_distances['combined'])} target tasks")
Specific Distance Types¶
# Molecule distances only
molecule_distances = task_distance.compute_molecule_distance(
molecule_featurizer="ecfp",
)
# Protein distances only
protein_distances = task_distance.compute_protein_distance(
protein_featurizer="esm2_t33_650M_UR50D",
)
# Combined distances with custom weights
combined_distances = task_distance.compute_combined_distance(
molecule_featurizer="ecfp",
weights={"molecules": 0.6, "protein": 0.4},
combination="weighted_average",
)
Working with Folds¶
Train–Test Analysis¶
Each distance matrix is indexed as distances[target_id][source_id]. With the
default source_fold=DataFold.TRAIN, train tasks are the sources (inner keys) and
the evaluated tasks are the targets (outer keys).
import pandas as pd
from themap.data.enums import DataFold
def analyze_train_test_distances(tasks, distance_type="molecules"):
"""Collect distances between train (source) and test (target) tasks."""
train_ids = set(tasks.get_task_ids(DataFold.TRAIN))
test_ids = set(tasks.get_task_ids(DataFold.TEST))
task_distance = TaskDistanceCalculator(tasks=tasks)
distances = task_distance.compute_all_distances()[distance_type]
pairs = []
for test_id in test_ids:
if test_id not in distances:
continue
for train_id in train_ids:
if train_id in distances[test_id]:
pairs.append({
"test_task": test_id,
"train_task": train_id,
"distance": distances[test_id][train_id],
})
return pairs
tt_distances = analyze_train_test_distances(tasks, "molecules")
print(f"Found {len(tt_distances)} train-test pairs")
# Closest training tasks for each test task
df = pd.DataFrame(tt_distances)
for test_task in df["test_task"].unique():
closest = df[df["test_task"] == test_task].nsmallest(3, "distance")
print(f"\nTest task {test_task} - closest training tasks:")
for _, row in closest.iterrows():
print(f" {row['train_task']}: {row['distance']:.3f}")
Cross-Validation Support¶
import random
from themap.data.enums import DataFold
def create_cv_folds(tasks, n_folds=5):
"""Create cross-validation folds from all task IDs."""
task_ids = (
tasks.get_task_ids(DataFold.TRAIN)
+ tasks.get_task_ids(DataFold.VALIDATION)
+ tasks.get_task_ids(DataFold.TEST)
)
random.shuffle(task_ids)
fold_size = len(task_ids) // n_folds
folds = []
for i in range(n_folds):
start = i * fold_size
end = start + fold_size if i < n_folds - 1 else len(task_ids)
test_tasks = task_ids[start:end]
train_tasks = [tid for tid in task_ids if tid not in test_tasks]
folds.append({"fold": i, "train": train_tasks, "test": test_tasks})
return folds
cv_folds = create_cv_folds(tasks, n_folds=5)
print(f"Created {len(cv_folds)} CV folds")
Caching and Performance¶
Feature Caching¶
Pass a cache_dir and THEMAP caches computed features there, reusing them on
subsequent loads.
tasks_with_cache = Tasks.from_directory(
directory="datasets/",
task_list_file="datasets/sample_tasks_list.json",
load_molecules=True,
load_proteins=True,
cache_dir="cache/", # features are cached here
)
print(f"Cache directory: {tasks_with_cache.cache_dir}")
Selective Loading¶
# Load only what you need for faster processing
molecule_only_tasks = Tasks.from_directory(
directory="datasets/",
task_list_file="datasets/sample_tasks_list.json",
load_molecules=True,
load_proteins=False, # skip protein loading
load_metadata=False, # skip other metadata
)
print("Loaded molecular data only for faster processing")
Advanced Task Operations¶
Task Filtering¶
def filter_tasks_by_size(tasks, min_molecules=10, max_molecules=1000):
"""Filter tasks by number of molecules."""
filtered = []
for task_id in all_task_ids(tasks):
task = tasks.get_task_by_id(task_id)
if task.molecule_dataset is None:
continue
n_molecules = len(task.molecule_dataset)
if min_molecules <= n_molecules <= max_molecules:
filtered.append(task_id)
return filtered
good_tasks = filter_tasks_by_size(tasks, min_molecules=20, max_molecules=500)
print(f"Found {len(good_tasks)} tasks with an appropriate size")
Inspecting Modalities¶
def summarize_modalities(tasks):
"""Count how many tasks carry each metadata modality."""
counts = {}
for task_id in all_task_ids(tasks):
task = tasks.get_task_by_id(task_id)
for modality in (task.metadata_datasets or {}):
counts[modality] = counts.get(modality, 0) + 1
for modality, n in counts.items():
print(f"Modality '{modality}': present in {n} tasks")
return counts
summarize_modalities(tasks)
Integration with External Tools¶
Export for Analysis¶
import pandas as pd
def export_task_summary(tasks, output_file="task_summary.csv"):
"""Export per-task information to CSV for external analysis."""
rows = []
for task_id in all_task_ids(tasks):
task = tasks.get_task_by_id(task_id)
rows.append({
"task_id": task_id,
"n_molecules": len(task.molecule_dataset) if task.molecule_dataset else 0,
"modalities": ",".join((task.metadata_datasets or {}).keys()),
})
df = pd.DataFrame(rows)
df.to_csv(output_file, index=False)
print(f"Task summary exported to {output_file}")
return df
task_df = export_task_summary(tasks)
Best Practices¶
Performance Tips¶
- Use caching: always specify a
cache_dir. - Load selectively: only load the modalities you need.
- Filter early: remove unsuitable tasks before distance computation.
- Batch processing: process tasks in groups for large datasets.
Memory Management¶
For large task sets, build smaller Tasks collections with the constructor
(there is no incremental add_task; pass fold lists instead).
from themap.data.enums import DataFold
def process_train_tasks_in_batches(tasks, batch_size=10):
"""Yield smaller Tasks collections over the train fold."""
train_ids = tasks.get_task_ids(DataFold.TRAIN)
for i in range(0, len(train_ids), batch_size):
batch_ids = train_ids[i:i + batch_size]
batch_tasks = Tasks(
train_tasks=[tasks.get_task_by_id(tid) for tid in batch_ids],
)
yield batch_tasks
for batch_tasks in process_train_tasks_in_batches(tasks, batch_size=5):
print(f"Processing batch with {len(batch_tasks)} tasks")
# Perform distance computation on the batch
Next Steps¶
- Explore performance optimization
- Learn about task hardness estimation
- Try advanced distance combination strategies
- Integrate with your machine learning pipeline