aspect is a Python library and command-line tool for reproducible featurisation of tabular machine-learning datasets.
It lets you define independent transformation pipelines for different input columns, including molecular structures, taxonomy, categorical variables, and numerical measurements. Pipelines can be serialised, applied to new datasets, checkpointed, and restored.
Aspect uses Providence for dataset loading, source provenance, and checkpointing, and Hugging Face Datasets for Arrow-backed data processing and caching.
The core idea:
Input dataset
│
├── SMILES ──────→ Molecular fingerprints
├── Species ─────→ Taxonomic fingerprints
├── Assay ───────→ Categorical encoding
├── MW ──────────→ Log transformation
└── Description ─→ Language-model embeddings
│
▼
Aspect DataPipeline
│
├── Reusable feature specification
├── Reproducible dataset checkpoint
└── Optional source materialisation
│
▼
Providence DataResolver
│
├── Hugging Face Dataset
├── Export: CSV / TSV / JSON / Parquet
└── Model-specific collation / DataLoader
Feature representations are stored in Arrow-compatible columns. Model-specific collators can convert these stored values into tensors, molecular graphs, or other runtime representations without embedding model-specific logic in dataset preprocessing.
- Quick start
- Installation
- Command-line interface
- Python API
- Feature specifications
- Available transforms
- Saving and loading pipelines
- Supported input and output formats
- Working with output columns
- Custom transforms
- Model integration
- Development
- Issues and documentation
Create a small CSV:
cat > compounds.csv <<'CSV'
compound_id,assay,mwt,pIC50
cmpd-1,MIC,46.07,5.0
cmpd-2,MIC,60.10,6.2
cmpd-3,IC50,78.11,4.8
CSVGenerate numeric and categorical features:
aspect featurize compounds.csv \
--features mwt:log@log_mwt assay:hash@assay_hash pIC50@target \
--extras compound_id \
--output features.parquetThe feature specification defines the transformations:
| Specification | Meaning |
|---|---|
mwt:log@log_mwt |
Apply natural logarithm to mwt |
assay:hash@assay_hash |
Hash the assay category into a fixed-length vector |
pIC50@target |
Copy pIC50 into a feature named target |
--extras compound_id |
Explicitly retain the compound identifier |
By default, Aspect also retains original input columns.
The same command can save a checkpoint alongside the exported table:
aspect featurize compounds.csv \
--features mwt:log@log_mwt assay:hash@assay_hash \
--checkpoint features.as \
--output features.parquetThe checkpoint records the feature specification, data source, and information needed to reconstruct the transformed dataset.
Reconstruct the transformed dataset from its checkpoint:
aspect write features.as \
--output restored.parquetExport it in another format:
aspect write features.as \
--output restored.csv.gzfrom aspect.data import DataPipeline
data = {
"compound_id": ["cmpd-1", "cmpd-2", "cmpd-3"],
"assay": ["MIC", "MIC", "IC50"],
"mwt": [46.07, 60.10, 78.11],
}
pipe = DataPipeline(
column_transforms={
"log_mwt": ("mwt", "log"),
"assay_hash": (
"assay",
{"name": "hash", "kwargs": {"ndim": 8}},
),
},
columns_to_keep=["compound_id"],
)
resolved = pipe(data, drop_unused_columns=True)
# Access the underlying Hugging Face Dataset
features = resolved.data
print(features.column_names)
print(features[:2])The transformed dataset contains:
['compound_id', 'log_mwt', 'assay_hash']
log_mwt contains a numeric feature with shape (n, 1), while assay_hash contains an (n, 8) feature matrix.
Save and reload the pipeline:
pipe.save("features.as")
restored = DataPipeline.load("features.as")
print(restored.data_out.data[:2])Aspect requires Python 3.11 or later.
pip install aspect-dataInstall only the features you need:
| Extra | Functionality | Installation |
|---|---|---|
chem |
Molecular fingerprints and descriptors via Schemist | pip install "aspect-data[chem]" |
bio |
Taxonomic fingerprints via Vectome | pip install "aspect-data[bio]" |
torch |
PyTorch integration | pip install "aspect-data[torch]" |
chemprop |
Chemprop molecular features | pip install "aspect-data[chemprop]" |
deep |
Transformer-based embeddings | pip install "aspect-data[deep]" |
git clone https://github.com/scbirlab/aspect.git
cd aspect
pip install -e ".[dev]"Aspect depends on Providence (>=0.0.2), which provides the common dataset-resolution and checkpointing infrastructure.
Aspect provides three commands:
aspect serialize
aspect featurize
aspect writeView available arguments with:
aspect --help
aspect featurize --help
aspect serialize --help
aspect write --helpCreate a reusable feature specification without processing a dataset:
aspect serialize \
--features mwt:log@log_mwt assay:hash@assay_hash \
--extras compound_id \
--output feature-spec.asThe specification can then be applied to other datasets:
aspect featurize new-compounds.csv \
--config feature-spec.as \
--output new-features.parquetApply a feature specification or saved pipeline to a dataset.
aspect featurize compounds.csv \
--features mwt:log@log_mwt assay:hash@assay_hash \
--output features.parquetA saved specification can be supplied with --config:
aspect featurize compounds.csv \
--config feature-spec.as \
--output features.parquetaspect featurize compounds.csv \
--config feature-spec.as \
--checkpoint features.asaspect featurize compounds.csv \
--config feature-spec.as \
--checkpoint features.as \
--force-write-dataThis requests materialisation of both source and transformed data inside the checkpoint, making it less dependent on the original dataset remaining available.
Without this option, Providence can retain immutable remote datasets by reference, avoiding unnecessary duplication. Local inputs are generally packaged by the current implementation.
aspect featurize large-table.parquet \
--start 1000 \
--end 2000 \
--features mwt:log@log_mwt \
--checkpoint subset.as--start is inclusive and --end is exclusive. The example selects rows 1000–1999.
aspect featurize compounds.csv \
--features assay:hash@assay_hash \
--cache .cache \
--output features.parquetExport data from a saved Aspect checkpoint:
aspect write features.as --output features.csv
aspect write features.as --output features.csv.gz
aspect write features.as --output features.parquet
aspect write features.as --output features.hfThe checkpoint is loaded through DataPipeline.load() and the output is delegated to Providence.
If the checkpoint contains only a feature specification and no source dataset, there is no transformed table to export.
The main class is DataPipeline.
from aspect.data import DataPipelineA pipeline maps input columns to named output features.
pipe = DataPipeline({
"log_mwt": ("mwt", "log"),
"assay_hash": (
"assay",
{"name": "hash", "kwargs": {"ndim": 16}},
),
})Pipelines can be applied to Python mappings, Pandas DataFrames, Hugging Face datasets, local files, or Providence resolvers.
# Dictionary
resolved = pipe({
"mwt": [46.07, 60.10],
"assay": ["MIC", "IC50"],
})
# Pandas DataFrame
resolved = pipe(df)
# Local file
resolved = pipe("compounds.csv")
# Hugging Face Hub dataset
resolved = pipe(
"hf://datasets/scbirlab/fang-2023-biogen-adme~scaffold-split:train"
)Aspect uses Providence's DataResolver to manage source datasets and transformed results.
from providence import DataResolver
from aspect.data import DataPipeline
source = DataResolver("compounds.csv")
pipe = DataPipeline({
"log_mwt": ("mwt", "log"),
})
result = pipe(source)
# Underlying datasets
input_ds = pipe.data_in.data
output_ds = result.data
# Source metadata
source_info = pipe.data_in.data_source
print(output_ds.column_names)DataPipeline.__call__() returns a DataResolver, rather than a raw Hugging Face dataset.
The underlying dataset remains available through .data, and provenance is tracked through the resolver.
pipe.write_table("features.parquet")
pipe.write_table("features.csv.gz")This delegates serialization to Providence.
For explicit row selection, create and slice the resolver before passing it to Aspect:
source = DataResolver("compounds.csv")
source = source.select_rows(start=100, end=200)
result = pipe(source)The same feature specification can be applied to multiple datasets:
pipe = DataPipeline({
"log_mwt": ("mwt", "log"),
})
train = pipe("train.csv")
test = pipe("test.csv")Each call returns the corresponding transformed resolver. The pipeline's data_in and data_out attributes refer to the most recent call.
Aspect supports both structured Python specifications and compact CLI strings.
The basic format is:
{
"output_column": ("input_column", "transform_name")
}Example:
pipe = DataPipeline({
"log_mwt": ("mwt", "log"),
})Pass a dictionary containing the transform name and keyword arguments:
pipe = DataPipeline({
"assay_hash": (
"assay",
{
"name": "hash",
"kwargs": {
"ndim": 32,
"seed": 123,
},
},
),
})Pass a sequence of transformations:
pipe = DataPipeline({
"scaled_log_mwt": (
"mwt",
[
"log",
{
"name": "scale",
"kwargs": {"factor": 0.1},
},
],
),
})The scale transform in this example is a custom transform that must be registered first.
Chains execute in the supplied order. Each transform must accept the representation produced by the preceding transform.
The CLI format is:
input_column:transform@output_column
| Syntax | Behaviour |
|---|---|
column |
Retain an input column |
column@new_name |
Identity transformation with a new name |
column:transform |
Apply a transform and automatically name the output |
column:transform@new_name |
Apply a transform with an explicit output name |
column:t1:t2@new_name |
Chain transformations |
column:transform(arg=value)@name |
Pass simple transform arguments |
Example:
aspect featurize compounds.csv \
--features \
mwt:log@log_mwt \
assay:hash(ndim=32)@assay_hash \
pIC50@target \
--output features.parquetSimple integers, floats, and booleans are parsed automatically. For complex or nested parameters, use the Python API.
Available with the standard installation:
| Transform | Input | Output |
|---|---|---|
identity |
Any supported column | Identity representation |
log |
Numeric | Natural logarithm |
hash |
Categorical/string | Deterministic fixed-length vector |
one-hot |
Categorical | One-hot vector |
Example:
pipe = DataPipeline({
"log_mwt": ("mwt", "log"),
"assay_hash": (
"assay",
{"name": "hash", "kwargs": {"ndim": 16}},
),
"assay_onehot": (
"assay",
{
"name": "one-hot",
"kwargs": {"categories": ["MIC", "IC50"]},
},
),
})Install with:
pip install "aspect-data[chem]"| Transform | Input | Output |
|---|---|---|
morgan-fingerprint |
SMILES | Molecular fingerprint |
descriptors-2d |
SMILES | 2D molecular descriptors |
descriptors-3d |
SMILES | 3D molecular descriptors |
Example:
data = {
"compound_id": ["ethanol", "ethylamine", "benzene"],
"smiles": ["CCO", "CCN", "c1ccccc1"],
}
pipe = DataPipeline(
{
"morgan": ("smiles", "morgan-fingerprint"),
"desc2d": ("smiles", "descriptors-2d"),
},
columns_to_keep=["compound_id"],
)
features = pipe(data, drop_unused_columns=True).dataInstall with:
pip install "aspect-data[bio]"The vectome-fingerprint transform encodes species names or taxonomic identifiers as fixed-length taxonomic representations.
data = {
"species": [
"Mycobacterium tuberculosis",
"Escherichia coli",
"Staphylococcus aureus",
],
}
pipe = DataPipeline({
"species_fp": (
"species",
{
"name": "vectome-fingerprint",
"kwargs": {"ndim": 128},
},
),
})
features = pipe(data).dataInstall with:
pip install "aspect-data[deep]"The hf-bart transform supports transformer-derived text embeddings.
data = {
"description": [
"cell wall inhibitor",
"protein synthesis inhibitor",
"DNA gyrase inhibitor",
],
}
pipe = DataPipeline({
"text_embedding": (
"description",
{
"name": "hf-bart",
"kwargs": {
"ref": "facebook/bart-base",
"aggregator": ["mean", "max"],
},
},
),
})
features = pipe(data).dataModel weights may be downloaded on first use.
Install with:
pip install "aspect-data[chemprop]"The chemprop-mol transform prepares molecular datapoints for Chemprop-style graph models.
Molecular graphs generally require model-specific batching. Aspect separates dataset storage from runtime collation so graph representations can be reconstructed when needed.
Aspect supports reusable feature specifications and provenance-aware dataset checkpoints.
Create a pipeline without applying it to any dataset:
pipe = DataPipeline({
"log_mwt": ("mwt", "log"),
"assay_hash": (
"assay",
{"name": "hash", "kwargs": {"ndim": 16}},
),
})
pipe.save("feature-spec.as")Restore and apply it later:
from aspect.data import DataPipeline
pipe = DataPipeline.load("feature-spec.as")
result = pipe({
"mwt": [46.07, 60.10],
"assay": ["MIC", "IC50"],
})pipe = DataPipeline({
"log_mwt": ("mwt", "log"),
})
result = pipe("compounds.csv")
pipe.save("features.as")The checkpoint records the pipeline configuration and a Providence checkpoint describing the input dataset.
Load it again:
restored = DataPipeline.load("features.as")
features = restored.data_out.dataBy default, when transformed data has not been explicitly saved, loading reconstructs it by applying the feature pipeline to the restored source dataset.
pipe.save(
"features.as",
save_transformed=True,
)This stores a transformed-data checkpoint alongside the source checkpoint.
pipe.save(
"features.as",
save_source_data=True,
save_transformed=True,
)This requests packaging of the input data and transformed data.
For remote datasets with immutable revisions, Providence can instead retain source references, allowing checkpoints to be substantially smaller.
Local sources are normally materialised to preserve reproducibility.
pipe = DataPipeline.load(
"features.as",
skip_call=True,
)This restores the specification and available checkpoint artefacts without automatically running the transformation pipeline.
A checkpoint may contain:
features.as/
├── config.json
├── data.pv/
└── transformed.pv/
config.json contains the pipeline specification and checkpoint metadata.
data.pv contains the Providence source checkpoint.
transformed.pv is included when transformed data is explicitly checkpointed.
The contents of Providence checkpoints depend on whether datasets are retained by reference or materialised.
The legacy methods save_checkpoint() and load_checkpoint() remain available as compatibility aliases, but new code should use save() and load().
Aspect accepts the input types supported by Providence, including:
| Input | Example |
|---|---|
| Python mapping | {"smiles": ["CCO", "CCN"]} |
| Pandas DataFrame | pipe(df) |
| Hugging Face Dataset | pipe(dataset) |
| Hugging Face IterableDataset | pipe(iterable_dataset) |
| Local file | pipe("data.csv") |
| Hugging Face dataset reference | pipe("hf://datasets/org/repo~config:split") |
| Providence DataResolver | pipe(resolved) |
Commonly supported formats include:
- CSV:
.csv,.csv.gz - Tab-separated:
.tsv,.tsv.gz,.txt,.txt.gz - JSON:
.json - Parquet:
.parquet - Arrow:
.arrow - XML:
.xml - Saved Hugging Face datasets:
.hf
The CLI and write_table() use Providence to select an output format from the filename.
| Extension | Format |
|---|---|
.csv |
CSV |
.csv.gz |
Gzip-compressed CSV |
.tsv, .txt |
Tab-separated text |
.tsv.gz, .txt.gz |
Gzip-compressed tab-separated text |
.json |
JSON |
.parquet |
Parquet |
.hf |
Hugging Face dataset directory |
| Unrecognised | Hugging Face dataset directory with .hf appended |
Providence supports versioned dataset references:
hf://datasets/<owner>/<repo>~<config>:<split>@<revision>
For example:
hf://datasets/scbirlab/fang-2023-biogen-adme~scaffold-split:train
The revision, configuration, and split are optional where supported by the resolver.
Pinning a dataset to an immutable commit revision improves reproducibility.
By default, Aspect retains original columns and adds transformed features.
out = pipe(data)To retain only transformed features and explicitly selected metadata:
pipe = DataPipeline(
{
"log_mwt": ("mwt", "log"),
"assay_hash": ("assay", "hash"),
},
columns_to_keep=["compound_id"],
)
out = pipe(
data,
drop_unused_columns=True,
)Extra columns can also be specified at call time:
out = pipe(
data,
drop_unused_columns=True,
keep_extra_columns=["compound_id", "target"],
)Access the underlying output dataset using:
dataset = out.data
print(dataset.features)
print(dataset.column_names)Custom transforms can be registered through Aspect's transform registry.
A transform factory receives configuration arguments and returns a callable that operates on data batches.
import numpy as np
from aspect.data import DataPipeline
from aspect.transform.registry import register_function
@register_function("square")
def Square():
def _square(data, input_column):
x = np.asarray(data[input_column], dtype=float)
return x ** 2
return _square
pipe = DataPipeline({
"mwt_squared": ("mwt", "square"),
})
out = pipe({
"mwt": [46.07, 60.10, 78.11],
}).data@register_function("scale")
def Scale(factor=1.0):
def _scale(data, input_column):
x = np.asarray(data[input_column], dtype=float)
return x * factor
return _scale
pipe = DataPipeline({
"scaled_mwt": (
"mwt",
{"name": "scale", "kwargs": {"factor": 0.001}},
),
})Custom transforms must be registered in the Python process before loading pipelines that reference them.
Aspect separates stored feature representations from model-specific runtime inputs.
The output dataset may contain numeric arrays, chemical fingerprints, taxonomic encodings, or other Arrow-compatible values.
For simple numerical models, extract the dataset:
features = pipe(data).dataFor PyTorch models, use the built-in DataLoader integration:
loader = pipe.dataloader(
batch_size=32,
shuffle=True,
)
for batch in loader:
# Batched, collated features
passThis requires the torch extra.
Transforms requiring specialised runtime representations can provide collators. These are applied when constructing model batches, allowing features such as molecular graphs to be stored independently from the framework-specific objects consumed by a model.
Custom collators can be provided through the collators argument:
loader = pipe.dataloader(
batch_size=32,
collators={
"custom_feature": custom_collator,
},
)Aspect can combine multiple representations in one dataset:
data = {
"compound_id": ["cmpd-1", "cmpd-2", "cmpd-3"],
"smiles": ["CCO", "CCN", "c1ccccc1"],
"assay": ["solubility", "clearance", "microsome"],
"species": [
"Escherichia coli",
"Mycobacterium tuberculosis",
"Staphylococcus aureus",
],
"mwt": [46.07, 45.08, 78.11],
"label": [0.1, 0.4, 0.8],
}
pipe = DataPipeline(
{
"morgan": ("smiles", "morgan-fingerprint"),
"desc2d": ("smiles", "descriptors-2d"),
"assay_hash": (
"assay",
{"name": "hash", "kwargs": {"ndim": 32}},
),
"species_fp": (
"species",
{"name": "vectome-fingerprint", "kwargs": {"ndim": 64}},
),
"log_mwt": ("mwt", "log"),
"y": ("label", "identity"),
},
columns_to_keep=["compound_id"],
)
features = pipe(
data,
drop_unused_columns=True,
).dataThis requires the chemistry and taxonomy extras.
Downstream models can independently consume molecular fingerprints, descriptors, categorical features, and taxonomic encodings, or combine suitable dense features into a common representation.
Install the development dependencies:
pip install -e ".[dev]"Run the test suite:
pytestRun module doctests:
pytest aspect --doctest-modulesRun the CLI integration tests:
bash test/scripts/run-tests.shAspect maintains separate GitHub Actions workflows for Python tests and CLI smoke tests.
Aspect is part of a small ecosystem for reproducible molecular machine-learning workflows:
- Providence — Dataset loading, source provenance, and checkpointing.
- Eluent — Chemistry-aware grouping, splitting, and cross-validation.
- Schemist — Molecular featurisation and cheminformatics.
- Vectome — Taxonomic and biological representations.
The packages can be used independently or composed into reproducible workflows from source data to model-ready features and evaluation splits.
Bug reports, feature requests, and contributions are welcome through the GitHub issue tracker.
Documentation: aspect.readthedocs.org
Source code: github.com/scbirlab/aspect
License: MIT.