Index¶
Source: src/memframe/wrappers/analytix/index.py
IndexWrapper is the public index interface exposed through a
ContextManager. It provides pandas-like set_index, reset_index, index,
reindex, and reindex_like backed by backend-native SQL across DuckDB,
PostgreSQL, and ClickHouse. SQL tables have no row labels, so the index is
metadata-only — a JSON list of key columns in
memframe_csv_registry.index_cols, never DDL. set_index/reset_index
only read/write that metadata; reindex/reindex_like are read-time
LEFT JOINs against the key.
Users normally call index methods directly on a dataset context returned by an upload operation:
The same methods are available asynchronously:
dataset = await mf.aupload_df(frame)
await dataset.aset_index("month")
aligned = await dataset.areindex([1, 2, 4])
The lower-level files are implementation details:
src/memframe/core/analytix/index/builds and executes backend-specific SQL (base.pyholds all logic;duckdb.py/postgres.py/clickhouse.pyinherit unchanged — the fill SQL is portable).src/memframe/core/orchestrator/analytix/index.pyresolves the active dataset context and passes persistence metadata (metadata writes are signature-only in the cache;reindexreads persist underdeep_cache).src/memframe/wrappers/analytix/index.pyexposes synchronous and asynchronous public methods.
Public API¶
Every index operation has synchronous and asynchronous forms (ctx.index is
the property form of get_index, returning bare label values):
| Synchronous | Asynchronous | Purpose |
|---|---|---|
set_index(keys, append=False, drop=True, verify_integrity=False) |
await aset_index(...) |
Track key column(s) as the logical index |
reset_index(level=None, drop=False) |
await areset_index(...) |
Clear the tracked index, or a subset of levels |
get_index(limit=None) / index |
await aget_index(...) |
Read back index columns and labels |
reindex(labels, ...) |
await areindex(...) |
Conform rows/columns to new labels |
reindex_like(other, ...) |
await areindex_like(...) |
Conform to another dataset's index and columns |
Public methods return the operation value directly (a DataFrame, label
list, or dict). Invalid operations raise OperationError.
Usage Overview¶
dataset = mf.upload_df(frame)
dataset.set_index("month")
labels = dataset.index # [1, 4, 7, 10]
aligned = dataset.reindex([1, 2, 4]) # gap months -> NaN
filled = dataset.reindex([1, 2, 4], fill_value=0)
dataset.reset_index() # back to synthetic RangeIndex
dataset = await mf.aupload_df(frame)
await dataset.aset_index(["year", "month"])
aligned = await dataset.areindex([(2012, 1), (2014, 5)], fill_value=0)
filled = await dataset.areindex([1, 2, 3, 4], method="ffill", limit=1)
nearest = await dataset.areindex([2, 9], method="nearest")
remaining = await dataset.areset_index(level="year") # keep ["month"]
Multi-key index and fill methods:
await orch.set_index(["year", "month"])
await orch.reindex([(2012, 1), (2014, 5)], fill_value=0)
await orch.reindex([1, 2, 3, 4], method="ffill", limit=1)
await orch.reindex([2, 9], method="nearest")
await orch.reset_index(level="year") # keep ["month"]
Parameters¶
| Parameter | Ops | Description |
|---|---|---|
keys |
set_index |
Column name or list. MultiIndex = list of 2+ columns (labels are tuples). columns= is accepted as an alias. |
append |
set_index |
True extends the tracked keys, False replaces them. Defaults to False. |
drop |
set_index / reset_index |
Recorded, not executed — key columns never leave the table; future projections may hide them. Defaults to True / False. |
verify_integrity |
set_index |
One COUNT(*) vs COUNT(DISTINCT keys) probe; fails on duplicates. Defaults to False. |
level |
reset_index |
None clears all levels; a name or list removes a subset. To rename levels, call rename() first — index ops never alter tables. |
labels / index / columns / axis |
reindex |
New labels: labels + axis (0/"index", 1/"columns"), or index=/columns= keywords. Both axes at once is allowed. |
method |
reindex |
None (gaps stay missing), "ffill"/"pad", "bfill"/"backfill", "nearest". Row axis only; needs monotonic labels. |
fill_value |
reindex |
Scalar for still-missing cells (applied after any method fill). Defaults to None (NULL). |
limit |
reindex |
Max consecutive fills for ffill/bfill. Needs a fill method. |
other |
reindex_like |
The other dataset's data_id string. Same key columns required; labels and columns are taken from it. Thin wrapper over reindex. |
Validation (each returns is_error True internally):
- Unknown key/column names; empty label lists.
methodoutsideffill/pad,bfill/backfill,nearest.methodwith non-monotonic labels;limitwithout a fill method.- Label width != index width;
reindexwith no index set (Call set_index(keys) first). reindex_likeagainst an unknowndata_id, an index-less dataset, or mismatched key columns.
Backend Behavior¶
Index ops support DuckDB, PostgreSQL, and ClickHouse with one shared implementation:
- Wanted labels are inlined as a portable
UNION ALLderived table (SELECT ? AS "k", ? AS "__ord" UNION ALL ...) with bind params, thenLEFT JOINed to the base table on null-safe key equality, ordered by the input ordinal. Read-only — no table is created or altered. ffill/bfilluse the islands idiom —COUNT(col)over the ordering plusMAX(col)per island — exact on all three backends (noIGNORE NULLSneeded).limitcaps the per-island run viaROW_NUMBER.nearest(single numeric/datetime key only) fetches oneDISTINCTkey list and maps gap labels to the closest present key in Python (ties go to the predecessor), then reuses the plain join path.- Identifiers are sanitized via
SQLIdentifierSanitizerand quoted per backend ("vs`). reindexon the column axis is a projection: missing columns render asfill_value(orNULL), extras are dropped, order follows the request.
Ceilings that fail loudly today: multi-key nearest, tolerance,
limit with nearest, and pandas level= broadcast — ask when a real query
needs them.
Errors¶
Failures return is_error True with an error_message (raised as
OperationError once the ContextManager wrapper lands):
No index set. Call set_index(keys) first.method='...' needs monotonically increasing/decreasing labels.Label width N != index width M./MultiIndex needs tuple labels ...Index mismatch: self [...] vs other [...] (same key columns required).method='nearest' needs a numeric or datetime key column.backend and data_id required— internal invariant, should not surface in normal use.
API Reference¶
memframe.wrappers.analytix.index.IndexWrapper
¶
Bases: IndexOrchestrator
Sync + async wrapper over IndexOrchestrator.
Metadata-only logical index (tracked in the registry, never DDL).
Naming convention:
async -> aset_index(), areset_index(), aget_index(), areindex(), ...
sync -> set_index(), reset_index(), get_index(), reindex(), ...
The ctx.index property returns the bare label values.
aset_index(keys=None, columns=None, append=False, drop=True, verify_integrity=False)
async
¶
Asynchronously track key column(s) as the logical index.
set_index(keys=None, columns=None, append=False, drop=True, verify_integrity=False)
async
¶
Synchronously track key column(s) as the logical index.
areset_index(level=None, drop=False, names=None)
async
¶
Asynchronously clear the tracked index, or a subset of levels.
reset_index(level=None, drop=False, names=None)
async
¶
Synchronously clear the tracked index, or a subset of levels.
aget_index(limit=None)
async
¶
Asynchronously read back index columns and labels.
get_index(limit=None)
async
¶
Synchronously read back index columns and labels.
areindex(labels=None, index=None, columns=None, axis=None, method=None, fill_value=None, limit=None)
async
¶
Asynchronously conform rows/columns to new labels.
reindex(labels=None, index=None, columns=None, axis=None, method=None, fill_value=None, limit=None)
async
¶
Synchronously conform rows/columns to new labels.
areindex_like(other, method=None, fill_value=None, limit=None)
async
¶
Asynchronously conform to another dataset's index and columns.
reindex_like(other, method=None, fill_value=None, limit=None)
async
¶
Synchronously conform to another dataset's index and columns.