A utility library for generating Polars expressions to work with nested data structures
Project description
Polars Nexpresso ☕
Polars Nexpresso is a utility library for working with nested and hierarchical data in Polars. It provides two main capabilities:
- Nested Expression Builder - Clean, intuitive syntax for transforming deeply nested structs and lists
- Hierarchical Packer - Pack/unpack operations for hierarchical data, similar to pandas MultiIndex but using Polars' native nested types
Nexpresso = Nested Expression + ☕ (espresso)
Installation
pip install polars-nexpresso
Or using uv:
uv add polars-nexpresso
Quick Start
Nested Expression Builder
Transform deeply nested data with intuitive dictionary syntax:
import polars as pl
from nexpresso import generate_nested_exprs
df = pl.DataFrame({
"order": [
{"customer": "Alice", "items": [{"name": "Laptop", "price": 999}, {"name": "Mouse", "price": 25}]},
{"customer": "Bob", "items": [{"name": "Keyboard", "price": 75}]},
]
})
# Define transformations declaratively
fields = {
"order": {
"items": {
"price": lambda x: x * 1.1, # 10% price increase
"discounted": pl.field("price") * 0.9, # New field
}
}
}
exprs = generate_nested_exprs(fields, df.schema, struct_mode="with_fields")
result = df.select(exprs)
Hierarchical Packer
Build and navigate hierarchical data from normalized tables:
from nexpresso import HierarchicalPacker, HierarchySpec, LevelSpec
# Define hierarchy
spec = HierarchySpec.from_levels(
LevelSpec(name="region", id_fields=["id"]),
LevelSpec(name="store", id_fields=["id"], parent_keys=["region_id"]),
)
packer = HierarchicalPacker(spec)
# Build from separate tables (like database tables)
nested = packer.build_from_tables({
"region": regions_df,
"store": stores_df,
})
# Navigate between granularities
flat = packer.unpack(nested, "store") # Explode to store level
packed = packer.pack(flat, "region") # Aggregate back to region level
Features
Nested Expression Builder
| Feature | Description |
|---|---|
| Field Selection | Keep fields as-is with None |
| Transformations | Apply lambdas: lambda x: x * 2 |
| New Fields | Create with pl.Expr: pl.field("a") + pl.field("b") |
| Deep Nesting | Works with any depth of structs/lists |
| Two Modes | select (keep specified) or with_fields (keep all) |
Hierarchical Packer
| Feature | Description |
|---|---|
| Build from Tables | Join normalized tables into nested hierarchy |
| Pack/Unpack | Navigate between granularity levels |
| Normalize/Denormalize | Split into per-level tables and reconstruct |
| Validation | Check for null keys and data integrity |
| Custom Separators | Use any separator (default: .) |
| Type Preservation | DataFrame in = DataFrame out |
Core Concepts
Field Value Types
When defining transformations:
None: Keep the field unchangeddict: Recursively process nested structuresCallable: Apply function to field (e.g.,lambda x: x * 2)pl.Expr: Create/modify field with full expression
Struct Modes
"select": Only keep fields specified in the dictionary"with_fields": Keep all fields, add/modify specified ones
Hierarchy Levels
Define your data hierarchy with LevelSpec:
LevelSpec(
name="store", # Level identifier
id_fields=["id"], # Unique key columns
parent_keys=["region_id"], # Foreign key to parent (for build_from_tables)
)
Examples
Lists of Structs
df = pl.DataFrame({
"items": [[{"name": "A", "qty": 5}, {"name": "B", "qty": 3}]]
})
fields = {
"items": {
"qty": lambda x: x * 2,
"total": pl.field("qty") * 10,
}
}
result = apply_nested_operations(df, fields, struct_mode="with_fields")
Conditional Logic
fields = {
"customer": {
"discount": pl.when(pl.field("tier") == "Gold")
.then(0.15)
.when(pl.field("tier") == "Silver")
.then(0.10)
.otherwise(0.05),
}
}
Building Hierarchies from Database Tables
# Tables with foreign key relationships
regions = pl.DataFrame({"id": ["west", "east"], "name": ["West", "East"]})
stores = pl.DataFrame({
"id": ["s1", "s2"],
"name": ["Store 1", "Store 2"],
"region_id": ["west", "east"]
})
spec = HierarchySpec.from_levels(
LevelSpec(name="region", id_fields=["id"]),
LevelSpec(name="store", id_fields=["id"], parent_keys=["region_id"]),
)
packer = HierarchicalPacker(spec)
nested = packer.build_from_tables({"region": regions, "store": stores})
# Result: Stores nested within their regions
Normalize and Denormalize
# Split nested data into separate tables
tables = packer.normalize(nested_df)
# {"region": region_df, "store": store_df, ...}
# Reconstruct from separate tables
rebuilt = packer.denormalize(tables)
API Reference
Nested Expressions
generate_nested_exprs(fields, schema, struct_mode="select")
Generate Polars expressions for nested data operations.
Parameters:
fields: Dictionary defining operations on columns/fieldsschema: DataFrame schema (or DataFrame/LazyFrame to extract schema)struct_mode:"select"or"with_fields"
Returns: list[pl.Expr]
apply_nested_operations(df, fields, struct_mode="select", use_with_columns=False)
Apply nested operations directly to a DataFrame.
Hierarchical Packer
HierarchicalPacker(spec, *, granularity_separator=".", escape_char="\\", preserve_child_order=True, validate_on_pack=True)
Main class for hierarchical operations.
Key Methods:
pack(frame, to_level)- Pack to coarser granularityunpack(frame, to_level)- Unpack to finer granularitynormalize(frame)- Split into per-level tablesdenormalize(tables)- Reconstruct from per-level tablesbuild_from_tables(tables)- Build hierarchy from normalized tablesvalidate(frame)- Check data integrity
HierarchySpec.from_levels(*levels, key_aliases=None)
Create a hierarchy specification from level definitions.
LevelSpec(name, id_fields, required_fields=None, order_by=None, parent_keys=None)
Define a single level in the hierarchy.
Running Examples
# Run comprehensive examples
python examples.py
# Or run specific module examples
python -m nexpresso.hierarchical_packer
Performance
Both components generate native Polars expressions, so performance is equivalent to hand-written code. All operations are lazy-compatible and benefit from Polars' query optimization.
License
MIT License - see LICENSE file for details.
Contributing
Contributions welcome! Please feel free to submit issues and pull requests.
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 polars_nexpresso-0.3.0.tar.gz.
File metadata
- Download URL: polars_nexpresso-0.3.0.tar.gz
- Upload date:
- Size: 156.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b4714321a5eaad83ef72fae097d30b59433438ebe720fa0f1ee27addf35828bd
|
|
| MD5 |
0c7ec28e2b94c81590a42ebcff3b5f93
|
|
| BLAKE2b-256 |
0793584c40df3c879f83426757c25e6836715b3b01e1a48fd8b805aebad88c37
|
Provenance
The following attestation bundles were made for polars_nexpresso-0.3.0.tar.gz:
Publisher:
publish.yml on heshamdar/polars-nexpresso
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polars_nexpresso-0.3.0.tar.gz -
Subject digest:
b4714321a5eaad83ef72fae097d30b59433438ebe720fa0f1ee27addf35828bd - Sigstore transparency entry: 1130133820
- Sigstore integration time:
-
Permalink:
heshamdar/polars-nexpresso@76908fca6273d4e27c94869f693112fb716eb0a2 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/heshamdar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@76908fca6273d4e27c94869f693112fb716eb0a2 -
Trigger Event:
release
-
Statement type:
File details
Details for the file polars_nexpresso-0.3.0-py3-none-any.whl.
File metadata
- Download URL: polars_nexpresso-0.3.0-py3-none-any.whl
- Upload date:
- Size: 38.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
92a5206d4e3e17ee9d359cb2f9711bef0eba78564101c619587b000d00f8c24e
|
|
| MD5 |
b3a3f05fe7e91abefb6266cefe319980
|
|
| BLAKE2b-256 |
758961d1e8fc594da81cca9efbe563041408cea99a218e9026a362158f44c084
|
Provenance
The following attestation bundles were made for polars_nexpresso-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on heshamdar/polars-nexpresso
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polars_nexpresso-0.3.0-py3-none-any.whl -
Subject digest:
92a5206d4e3e17ee9d359cb2f9711bef0eba78564101c619587b000d00f8c24e - Sigstore transparency entry: 1130133919
- Sigstore integration time:
-
Permalink:
heshamdar/polars-nexpresso@76908fca6273d4e27c94869f693112fb716eb0a2 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/heshamdar
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@76908fca6273d4e27c94869f693112fb716eb0a2 -
Trigger Event:
release
-
Statement type: