Write a dataframe pipeline once, run it on Dask or pandas
Project description
BetterFrame
Write a dataframe pipeline once, run it on Dask or pandas.
Not a DataFrame implementation. BetterFrame adapts the execution semantics
that differ between the two engines — per-partition application, meta schemas,
aggregation keywords, persistence — so a pipeline that has to work on both does
not fill up with if is_dask: branches.
Why
Dask is the right tool when data outgrows memory, and a needless tax when it does not. A pipeline that wants both ends up either duplicated or littered with engine checks. The differences are few but awkward:
| Dask | pandas | |
|---|---|---|
| apply a function per partition | df.map_partitions(fn, meta=…) |
fn(df) |
| index level names | df.index._meta.names |
df.index.names |
groupby().agg() extras |
split_out=… |
— |
| make the result concrete | df.persist() |
already is |
| column assignment | builds a graph | mutates in place |
Install
pip install betterframe # pandas only
pip install "betterframe[dask]" # to drive Dask frames as well
Use
from betterframe import BetterFrame
def compute(records):
frame = BetterFrame(records).mutable() # records may be Dask or pandas
frame = frame.apply(normalise, meta=lambda: build_meta(frame.meta_source()))
result = frame.pipe(
lambda df, kw=frame.agg_kwargs(): df.groupby("key").agg({"scaled": "sum"}, **kw)
)
return result.finalize()
BetterFrame binds a frame to the operations its engine needs, so the frame
stops being an argument to every call. Methods that produce a frame return a
BetterFrame, so a pipeline chains; finalize() ends the chain and hands back
a native frame, and native reaches the underlying one at any point.
It is deliberately thin — it does not proxy the dataframe API. Real work
still happens on the frame itself, through pipe() or native. Wrapping exists
to answer engine questions, not to replace pandas.
The same function now runs on either engine, and there is exactly one place — this package — that knows the difference.
The part worth knowing about
Most of the surface is mechanical. The subtle part is Dask's meta handling,
because getting it wrong produces frames that differ only in dtype, only for
empty inputs, and only sometimes:
- Dask coerces every partition to a meta schema. A helper that short-circuits on an empty frame and returns it unchanged still comes out with the populated schema, because Dask repairs it afterwards.
- Where no explicit meta is given, Dask infers one by running the function against a synthetic non-empty frame. So even without a meta, an empty partition ends up with the schema the function would have produced had there been rows.
pandas does neither. PandasOps.apply reproduces both, so an empty frame is
not silently a different dtype depending on which engine produced it. This is
the failure mode BetterFrame exists to prevent: it is invisible in every test
whose fixtures happen to be fully populated.
Extending
Subclass DaskOps / PandasOps for engine-specific behaviour of your own —
custom aggregations, say — and hand the subclasses to BetterFrame:
from betterframe import BetterFrame, DaskOps, PandasOps
class MyDaskOps(DaskOps):
def set_union(self):
return some_dask_aggregation()
class MyPandasOps(PandasOps):
def set_union(self):
return some_pandas_callable
frame = BetterFrame(records, dask_ops=MyDaskOps, pandas_ops=MyPandasOps)
frame.ops.set_union() # `ops` reaches engine questions that take no frame
Domain-specific aggregations are deliberately left out of the core so the package stays about engine semantics.
Development
uv sync --dev
uv run pytest
uv run ruff check .
uv run ruff format --check .
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file betterframe-0.1.0.tar.gz.
File metadata
- Download URL: betterframe-0.1.0.tar.gz
- Upload date:
- Size: 75.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f292d9828bcc2ceac4f3110b4921e328f11dcc86501eda69182c7ddba9671447
|
|
| MD5 |
e061b2ff9557505e9ed262388c4de07a
|
|
| BLAKE2b-256 |
525d52868b6f3b91c39bb5873252fbbade5b41e56e7dcbae251204291082dace
|
Provenance
The following attestation bundles were made for betterframe-0.1.0.tar.gz:
Publisher:
release.yaml on izzet/betterframe
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
betterframe-0.1.0.tar.gz -
Subject digest:
f292d9828bcc2ceac4f3110b4921e328f11dcc86501eda69182c7ddba9671447 - Sigstore transparency entry: 2263349553
- Sigstore integration time:
-
Permalink:
izzet/betterframe@ca2a277cb14b30ef63243061a30f7891264cf0b6 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/izzet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@ca2a277cb14b30ef63243061a30f7891264cf0b6 -
Trigger Event:
release
-
Statement type:
File details
Details for the file betterframe-0.1.0-py3-none-any.whl.
File metadata
- Download URL: betterframe-0.1.0-py3-none-any.whl
- Upload date:
- Size: 8.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f3a456986af3a0169bdad2c69dffa51dc68650f1693c72ec9ea76f66f5ee8637
|
|
| MD5 |
945a178cdc13bc6f61551adae2c781e7
|
|
| BLAKE2b-256 |
662507db476439387d37f5e10b7855f5caba56d34d3c2d61a1e22fbe9a208ed0
|
Provenance
The following attestation bundles were made for betterframe-0.1.0-py3-none-any.whl:
Publisher:
release.yaml on izzet/betterframe
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
betterframe-0.1.0-py3-none-any.whl -
Subject digest:
f3a456986af3a0169bdad2c69dffa51dc68650f1693c72ec9ea76f66f5ee8637 - Sigstore transparency entry: 2263349765
- Sigstore integration time:
-
Permalink:
izzet/betterframe@ca2a277cb14b30ef63243061a30f7891264cf0b6 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/izzet
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yaml@ca2a277cb14b30ef63243061a30f7891264cf0b6 -
Trigger Event:
release
-
Statement type: