from mixtrain import DatasetConstructor
Dataset(name: str, *, version: int | None = None)Creates a reference to an existing dataset on the platform. This is a lazy reference, no API call is made until you access data or schema.
| Parameter | Type | Description |
|---|---|---|
name | str | Dataset name |
version | int | None | Optional dataset version (v1, v2, ...). None reads the latest version. |
dataset = Dataset("training-data")
baseline = Dataset("training-data", version=3)Properties
| Property | Type | Description |
|---|---|---|
name | str | Dataset name |
schema | pyarrow.Schema | Column schema |
description | str | Dataset description |
column_types | dict[str, str] | Semantic column types (image, video, ...) |
metadata | dict | Platform metadata (schema, column types, statistics, snapshots) |
version | int | None | The version of the dataset. None for non-platform datasets. |
from_version | int | None | The lower version bound of this Dataset view if any, None otherwise |
Versions
Mixtrain datasets are versioned on each update.
latest = Dataset("training-data")
v3 = Dataset("training-data", version=3)list_versions()
dataset.list_versions() -> list[DatasetVersion]List dataset versions, newest first. Each entry includes version, snapshot_id, operation, added_records, total_records, and timestamp_ms when available.
for version in Dataset("photos").list_versions():
print(version.version, version.operation)added_since()
dataset.added_since(
from_version: int | None = None,
to_version: int | None = None,
) -> Datasetadded_since returns the rows added or changed between two versions — present at to_version and new or modified since from_version. from_version is exclusive and to_version is inclusive; from_version=None starts from the earliest version and to_version=None ends at the latest. Unchanged and deleted rows do not appear.
Dataset-triggered routines use added_since/deleted_since to process just the new rows, and have from_version and version properties to identify the version range they cover.
deleted_since()
dataset.deleted_since(
from_version: int | None = None,
to_version: int | None = None,
) -> Datasetdeleted_since returns the rows present at from_version and gone at to_version. from_version is exclusive and to_version is inclusive; from_version=None starts from the earliest version and to_version=None ends at the latest.
as_read_only()
dataset.as_read_only() -> DatasetReturn a read-only version of this dataset. Calling in-place mutations (append()/update()/delete()/drop()/set_column_types()/set_description()) would raise an error. Reads and non-mutating transformations will continue to work.
ro = Dataset("photos").as_read_only()
ro.sql("SELECT * FROM photos").to_pandas() # ok
ro.append(rows) # raises: read-only datasetIteration
Row iteration
for row in dataset:
print(row) # {"col1": value, "col2": value}Streams rows without loading the full dataset into memory. dataset.rows(load_files=...)
is the explicit form; file-backed columns arrive as Image/Video/File handles by
default (load_files="auto"), as raw records with load_files=False, or as loaded
content with load_files="bytes" / "path" / a per-column dict. See load_files().
batches()
dataset.batches(
size: int = None,
format: str = "dict", # "dict" | "arrow"
load_files: bool | str | dict = "auto",
) -> Iterator[dict[str, list]] | Iterator[pyarrow.RecordBatch]Streams batches. size=None keeps the plan's natural chunk sizes. format="dict"
yields columnar dicts (file columns as handles under "auto"); format="arrow" yields
RecordBatches (file columns stay native record structs under "auto"). The explicit
load_files values (False, "bytes", "path", per-column dict) mean the same thing
in every format.
for batch in dataset.batches(64):
print(batch) # {"col1": [v1, v2, ...], "col2": [v1, v2, ...]}
for batch in dataset.batches(64, load_files="bytes"):
send(batch["image"]) # list[bytes], fetched in parallelCreating Datasets
From files
Dataset.from_file(file_path: str) -> DatasetLoad a local data file into an in-memory dataset. Use .save() to persist
it to Mixtrain.
Supported formats: .csv, .parquet, .jsonl (all stream in batches)
dataset = Dataset.from_file("data.parquet")
dataset.save("training-data", description="Training dataset")From in-memory data
# From Python dict
ds = Dataset.from_dict({"col1": [1, 2, 3], "col2": ["a", "b", "c"]})
# From pandas DataFrame
ds = Dataset.from_pandas(df)
# From Arrow table
ds = Dataset.from_arrow(table)
# From a re-iterable batch source
ds = Dataset.from_batches(batch_factory, schema=schema)
# From HuggingFace datasets (streams by default)
ds = Dataset.from_huggingface("imdb", split="train")
# From PyTorch dataset (schema= is required)
ds = Dataset.from_torch(
torch_dataset,
schema=pa.schema([("data", pa.float32()), ("label", pa.int64())]),
)These create in-memory or streaming datasets. Use .save() to persist to
the platform.
Empty datasets
Dataset.empty(columns: list[str] | dict[str, type]) -> DatasetCreate a zero-row dataset to fill later with append(). You can pass a list of column names or a dict of column names to types. A list of names defaults every column to str. When passing a dictionary, the values can be Python types
(str, int, float, bool, bytes), Mixtrain types (Image, Embedding, ...), type names ("image"), or PyArrow types.
from mixtrain import Dataset, Image
# All columns default to str
ds = Dataset.empty(["prompt", "photo", "score"]).save("my-dataset")
# Or specify types
ds = Dataset.empty({"prompt": str, "photo": Image, "score": float}).save("my-dataset")If column types are not specified, the dataset will create str default columns that adopt the type of the first append(). Explicitly typed columns keep their type, and appending that doesn't match will fail with an error.
Mutating Datasets
append()
dataset.append(
source: Dataset | Table | DataFrame | dict | list[dict],
copy_files: bool | str = "auto",
stats: bool = True,
workers: int = 8,
) -> DatasetAppend rows from source to an existing platform dataset. source may be another Dataset, a PyArrow Table, a pandas DataFrame, a dict of columns, or a list of row dicts. Dict inputs are column-oriented, so each value must be a sequence. copy_files, workers, and stats behave the same as in save().
# Append rows to an existing dataset
ds = Dataset("my-dataset")
# Append multiple rows
ds.append([{"img": "fox.jpg", "label": 2}, {"img": "owl.jpg", "label": 1}])
# Append 2 rows of column-oriented data
ds.append({"img": ["owl.jpg", "bear.jpg"], "label": [1, 3]})
# The rows can come from another Dataset
new_data = Dataset("new-data")
ds.append(new_data)update()
dataset.update(predicate: str | None = None, /, **column_values) -> DatasetSet column values on the rows matching predicate. predicate uses the same expression language as filter() to choose the rows to update. Each keyword argument is a column assignment: column=value writes that constant onto every matched row. Predicate=None updates all rows. Columns that don't exist yet are added. At least one column=value is required.
Dataset("photos").update("score < 0", flag=True) # update low-scoring rows with column flag set to True
Dataset("photos").update(verified=True, label=1) # update all rows with column verified set to True and label set to 1delete()
dataset.delete(predicate: str) -> DatasetDelete the rows matching predicate. predicate uses the same expression language as filter(). The predicate is required — a bare delete() is refused so it can't silently wipe the dataset. Use drop() to remove the whole dataset.
Dataset("photos").delete("score < 0") # remove low-scoring rowsupdate() and delete() run on a saved dataset, not on a derived view. So d1.filter(...).delete(...) or d1.map(...).update(...) would raise an error. Use the predicate to select the rows to change instead: d1.delete("score < 0").
save()
dataset.save(
name: str,
description: str = None,
column_types: dict | str | None = "auto",
copy_files: bool | str = "auto",
stats: bool = True,
workers: int = 8,
) -> DatasetSave dataset to the platform. Files referenced by file-backed columns are handled automatically: local files are uploaded so they can be viewed and used from Mixtrain, while remote URLs are kept as references by default.
| Parameter | Type | Description |
|---|---|---|
name | str | Dataset name to create on the platform |
description | str | Optional description |
column_types | dict | str | None | "auto" (default) infers types for untyped columns. A dict sets exactly the named columns (no inference of others). None disables type detection. To set some columns and infer the rest, chain with_column_types() before save(). |
copy_files | bool | str | "auto" (default) uploads local files and keeps remote URLs as references. True also copies remote files into workspace storage. False writes the table as-is without touching file contents. |
stats | bool | Record intrinsic file stats (image width/height, video/audio duration_seconds, plus fields like fps, num_frames, sample_rate, and channels) per media column during upload. Queryable via sql() struct access, e.g. image.width >= 512. |
workers | int | Threads uploading file/tensor blobs in parallel (default 8). Raise it when uploading many files doesn't saturate your upload bandwidth. |
# Auto-detects column types and uploads local files (default)
ds = Dataset.from_dict({"img": ["cat.jpg", "dog.png"], "label": [0, 1]})
ds.save("my-dataset") # img detected as Image, files uploaded
# Explicit types for exactly these columns (no inference of others)
from mixtrain import Image
ds.save("my-dataset", column_types={"photo": Image})
# Pin some columns and let save() infer the rest: chain with_column_types()
ds.with_column_types({"photo": Image}).save("my-dataset")
# Copy remote URLs into workspace storage too
ds.save("my-dataset", copy_files=True)drop()
dataset.drop() -> dictRemove the entire dataset from the platform. This is a destructive operation and cannot be undone.
Export Methods
collect()
dataset.collect(max_bytes: int = None, warn: bool = True) -> pyarrow.TableExecute the plan and materialize the result as an Arrow table. Warns on large results;
pass max_bytes to fail instead of materializing more than a budget.
to_arrow() / to_pandas()
dataset.to_arrow() -> pyarrow.Table
dataset.to_pandas() -> pandas.DataFrameMaterialize as an Arrow table or pandas DataFrame (equivalent to collect()).
to_tensors()
dataset.to_tensors(missing: str = "nan") -> dict[str, Tensor | list]Convert to dict of PyTorch tensors. Uses zero-copy for numeric columns. missing
controls null handling — see Missing values.
to_torch()
dataset.to_torch(
batch_size: int = None,
num_workers: int = 0,
rank: int = 0,
world_size: int = 1,
drop_last: bool = False,
load_files: bool | str | dict = "auto",
prefetch_batches: int = 2,
workers: int = 32,
max_inflight_bytes: int = 256_000_000,
fetch_timeout: float = 30.0,
missing: str = "nan",
**dataloader_kwargs,
) -> DataLoaderGet a PyTorch DataLoader. Data is sharded automatically across DataLoader workers and
distributed ranks (rank/world_size) so each worker reads a disjoint partition and
fetches only its own media files. Workers share one on-disk blob cache
(MIXTRAIN_CACHE_DIR), so files fetched in one epoch aren't re-downloaded in the next.
File-backed columns are loaded automatically (load_files= accepts the same values
as load_files(); False disables); batches are pipelined with background prefetch.
workers controls how many files each DataLoader worker fetches in parallel and max_inflight_bytes caps the bytes held in flight while fetching.
fetch_timeout sets the per-request timeout, and MIXTRAIN_CACHE_DIR controls the local blob cache location. missing controls null handling for numeric/tensor columns — see
Missing values. Extra kwargs (e.g. collate_fn, pin_memory) pass
through to torch.utils.data.DataLoader. Shuffle with .shuffle() before
calling to_torch() and use loader.set_epoch(epoch) to reshuffle windowed shuffles per epoch.
# Batched - yields dicts of tensors (numeric columns are zero-copy)
loader = dataset.to_torch(batch_size=32, num_workers=4)
for batch in loader:
print(batch["features"].shape) # torch.Size([32, ...])
# Media: images arrive as bytes — convert in collate_fn (runs in workers)
import io
from PIL import Image as PILImage
def collate(batch):
batch["image"] = [PILImage.open(io.BytesIO(b)) for b in batch["image"]]
return batch
loader = dataset.to_torch(batch_size=64, num_workers=4, collate_fn=collate)See Best practices for checkpoint/resume and null handling.
to_huggingface()
dataset.to_huggingface() -> datasets.DatasetConvert to HuggingFace Dataset.
Transformations
All transformations are lazy and return a new Dataset (immutable).
shuffle()
dataset.shuffle(seed: int = None, global_: bool = True, window: int = 10_000) -> DatasetRandomly shuffle rows. global_=True is a full shuffle (may buffer in DuckDB);
global_=False is a bounded-memory streaming window shuffle. Without a seed, a stable
seed is generated so re-iteration is consistent.
sample()
dataset.sample(n: int, seed: int = None) -> DatasetRandom sample of n rows (streaming reservoir sample).
select()
dataset.select(columns: list[str]) -> DatasetKeep only the named columns. Pushes the projection down to the storage scan when possible.
take()
dataset.take(indices: list[int]) -> DatasetKeep only the rows at the given indices.
head() / slice()
dataset.head(n: int = 5) -> Dataset
dataset.slice(start: int, stop: int) -> DatasetFirst n rows / a contiguous row range. Limits push down to the storage scan, so
Dataset("big").head(5) reads only a few rows.
filter()
dataset.filter(predicate: str | Callable[[dict], bool]) -> DatasetFilter rows with a SQL-like expression string or a Python function. Expressions are evaluated vectorized and push down into the Iceberg scan when the filter sits directly on a platform table — prefer them for large datasets; functions run per row in Python.
recent = dataset.filter("score > 0.8 and label != None")
positive = dataset.filter(lambda x: x["label"] == 1)map() / map_batches()
dataset.map(fn: Callable[[dict], dict], *, schema: pyarrow.Schema) -> Dataset
dataset.map_batches(fn: Callable[[RecordBatch], RecordBatch], *, schema: pyarrow.Schema) -> DatasetApply a function per row, or per Arrow batch (faster for vectorized work). The output
schema is required so the pipeline stays lazy without executing your function early.
import pyarrow as pa
ds = dataset.map(
lambda x: {"text": x["text"], "text_len": len(x["text"])},
schema=pa.schema([("text", pa.string()), ("text_len", pa.int64())]),
)join()
dataset.join(other: Dataset, keys: str, join_type: str = "inner") -> DatasetJoin with another dataset
| Parameter | Type | Description |
|---|---|---|
other | Dataset | Right table to join |
keys | str | Column to join on |
join_type | str | "inner", "left outer", "right outer", "full outer" |
joined = users.join(orders, keys="user_id")train_test_split()
dataset.train_test_split(*, test_size: float = 0.2, seed: int = None) -> dict[str, Dataset]Split into deterministic, non-overlapping train and test sets.
splits = dataset.train_test_split(test_size=0.2, seed=42)
train_ds = splits["train"]
test_ds = splits["test"]shard()
dataset.shard(index: int, count: int) -> DatasetRestrict the dataset to one of count disjoint partitions (for manual distributed
reading; to_torch() does this automatically).
File Methods
load_files()
dataset.load_files(
to: bool | str | dict = "auto",
columns: list[str] = None,
workers: int = 32,
max_inflight_bytes: int = 256_000_000,
on_error: str = "raise",
fetch_timeout: float = 30.0,
) -> DatasetLoad file-backed columns locally. Files are downloaded in parallel and cached
(in $MIXTRAIN_CACHE_DIR), so re-iteration does not
re-download them.
to controls what each file-backed column becomes:
| Value | Column value | Use for |
|---|---|---|
"auto" (default) | bytes for image/file, local path for video/audio/3d/mcap/rrd | most cases |
"bytes" | raw bytes | GPU decode, sending to APIs |
"path" | local file path (str, read-only) | decoders that want seekable files |
False | raw media record | manual control |
Pass a dict for per-column values: load_files({"image": "path"}) — a dict is an
exact spec — only the columns it names are loaded; other media columns are left as
records (lazy handles when consumed as Python objects). on_error="null" maps failed
fetches to None instead of raising.
with_column_types()
dataset.with_column_types(column_types: str | dict = "auto") -> DatasetSet column types (image, video, audio, ...) on the schema. "auto" infers from a data
sample; pass a dict for explicit mappings. save() applies this automatically, so
you only need it to override inference before saving or calling load_files().
SQL Queries
sql()
dataset.sql(sql: str, table_name: str = "data", seed: int = None) -> DatasetExecute SQL via DuckDB. The dataset is registered as data by default, or pass
table_name to choose another alias. query() is an alias.
filtered = dataset.sql("SELECT * FROM data WHERE score > 0.8")
stats = dataset.sql("SELECT label, COUNT(*) as cnt FROM t GROUP BY label", table_name="t")Inspection
explain()
dataset.explain() -> strRender the lazy query plan.
print(Dataset("d").filter("x > 1").head(5).explain())cache()
dataset.cache(path: str = None, load_files: bool = True) -> DatasetMaterialize the dataset locally so iteration is fast and offline-safe.
With no arguments, platform table scans are mirrored to local Parquet under
$MIXTRAIN_CACHE_DIR/datasets/<name>/<snapshot_id> — reused across runs and
processes, and invalidated automatically when the table gets a new snapshot.
Transforms in the plan re-run against the local mirror. With path=, this
dataset's full output is materialized at an explicit location and reused on
later calls (the caller owns invalidation). load_files=True also fetches
file-backed column contents into the local file cache.
ds = Dataset("photo-dataset").cache() # table + files local; train offline
prep = ds.filter("image.width >= 512").cache(path="/data/prep-v2")set_column_types()
dataset.set_column_types(column_types: dict) -> NoneUpdate column types for rich UI rendering.
from mixtrain import Image, Audio
dataset.set_column_types({"image_url": Image, "audio_url": Audio})set_description()
dataset.set_description(description: str) -> dictSet the dataset's description.
Class Methods
Dataset.exists()
Check if a dataset exists.
Dataset.exists(name: str) -> boolif not Dataset.exists("my-dataset"):
Dataset.from_pandas(df).save("my-dataset")Helper Functions
list_datasets()
from mixtrain import list_datasets
datasets = list_datasets()
for ds in datasets:
print(ds.name)get_dataset()
from mixtrain import get_dataset
dataset = get_dataset("my-dataset")Best practices
Guidance for feeding datasets into a training loop reliably and efficiently.
Checkpoint and resume
The loader from to_torch() can checkpoint mid-epoch and resume where it
left off, so a job that dies partway through an epoch doesn't restart it. Save
loader.state_dict() alongside your model/optimizer state and call
loader.load_state_dict(...) after restart, before iterating:
state = loader.state_dict() # checkpoint position
# ... later, on a fresh loader ...
loader.load_state_dict(state) # resume the interrupted epochResume against the same pipeline as the checkpoint: the same dataset (pin the version
with Dataset(name, version=...)), a pinned shuffle seed (shuffle(seed=...)), and the
same num_workers, world_size, and batch_size. The loader checks these and raises on
a mismatch rather than resuming the wrong data. With num_workers <= 1 resume is exact;
with more workers it resumes from the nearest batch boundary, so a few already-seen
batches may repeat.
Missing values
to_torch() and to_tensors() take a missing argument
that decides how nulls in numeric and tensor columns become dense tensors:
"nan"(default) — nulls becomeNaN. Float columns fill directly; integer and boolean columns are promoted to float (and filled withNaN) only when the dataset recorded the column as nullable, so a column's dtype stays the same across batches. An integer/boolean column that was never saved (so nullability is unknown) and happens to contain a null raises — re-save the dataset, cast the column to float, or filter the nulls out first."error"— raise on any null in a numeric or tensor column.
Non-numeric columns keep None under either policy.
Scaling reads and uploads
- Upload throughput — raise
save()'sworkerswhen uploading many files doesn't saturate your link. - Download-bound reads — raise
from_huggingface()'sworkersto read more shards concurrently and keep a latersave()fed.