PolarsGrouper
PolarsGrouper is a Rust-based extension for Polars that provides efficient graph analysis capabilities, with a focus on component grouping and network analysis.
Core Features
Component Grouping
super_merger: Easy-to-use wrapper for grouping connected componentssuper_merger_weighted: Component grouping with weight thresholds- Efficient implementation using Rust and Polars
- Works with both eager and lazy Polars DataFrames
Hierarchy / BOM Explosion
hierarchy_totals,hierarchy_levels,hierarchy_paths: explode a parent → child edge list (bill of materials, chart of accounts, WBS) into its transitive closure, with quantities rolled up along every path- Replaces
WITH RECURSIVE/CONNECT BYqueries and for-loops, and runs inside lazy queries
Additional Graph Analytics
- Shortest Path Analysis: Find shortest paths between nodes
- PageRank: Calculate node importance scores
- Betweenness Centrality: Identify key bridge nodes
- Association Rules: Discover item relationships and patterns
Installation
pip install polars-grouper
# For development:
python -m venv .venv
source .venv/bin/activate
maturin develop
Usage Examples
Basic Component Grouping
The core functionality uses super_merger to identify connected components:
import polars as pl
from polars_grouper import super_merger
df = pl.DataFrame({
"from": ["A", "B", "C", "D", "E", "F"],
"to": ["B", "C", "A", "E", "F", "D"],
"value": [1, 2, 3, 4, 5, 6]
})
result = super_merger(df, "from", "to")
print(result)
Weighted Component Grouping
For cases where edge weights matter:
from polars_grouper import super_merger_weighted
df = pl.DataFrame({
"from": ["A", "B", "C", "D", "E"],
"to": ["B", "C", "D", "E", "A"],
"weight": [0.9, 0.2, 0.05, 0.8, 0.3]
})
result = super_merger_weighted(
df,
"from",
"to",
"weight",
weight_threshold=0.3
)
print(result)
Hierarchy / BOM Explosion
Resolve a bill of materials in one lazy expression. Quantities multiply along a path (4 wheels × 5 screws) and add up across paths (+ 20 screws directly on the car):
import polars as pl
from polars_grouper import hierarchy_totals, hierarchy_levels, hierarchy_paths
bom = pl.LazyFrame({
"parent": ["car", "car", "car", "wheel", "wheel", "wheel", "rim"],
"child": ["wheel", "steering_wheel", "screw", "tyre", "rim", "screw", "iron"],
"qty": [4.0, 1.0, 20.0, 1.0, 1.0, 5.0, 2.5],
})
totals = bom.select(hierarchy_totals("parent", "child", "qty").alias("bom")).unnest("bom")
# Raw materials needed for one car
totals.filter(pl.col("ancestor") == "car", pl.col("is_leaf")).collect()
# ┌──────────┬────────────────┬───────┬──────────┬─────────┐
# │ ancestor ┆ descendant ┆ level ┆ quantity ┆ is_leaf │
# ╞══════════╪════════════════╪═══════╪══════════╪═════════╡
# │ car ┆ steering_wheel ┆ 1 ┆ 1.0 ┆ true │
# │ car ┆ screw ┆ 1 ┆ 40.0 ┆ true │
# │ car ┆ tyre ┆ 2 ┆ 4.0 ┆ true │
# │ car ┆ iron ┆ 3 ┆ 10.0 ┆ true │
# └──────────┴────────────────┴───────┴──────────┴─────────┘
Three functions, one per level of detail. They share column names, so each is an aggregation of the next:
| function | one row per | extra columns |
|---|---|---|
hierarchy_totals |
ancestor, descendant | level is the shallowest level |
hierarchy_levels |
ancestor, descendant, level | |
hierarchy_paths |
path (an indented BOM) | parent, quantity_per, path |
All three take top_level_only (explode finished products only), include_self (add a
level-0 row per node) and max_depth. A cycle in the data raises an error that names it.
The same works for any hierarchy. To roll up general-ledger balances along a chart of accounts, link every account to itself and to everything below it, then aggregate:
closure = accounts.select(
hierarchy_totals("parent_account", "account", include_self=True).alias("closure")
).unnest("closure")
balances = (
closure.join(transactions, left_on="descendant", right_on="account")
.group_by("ancestor")
.agg(pl.col("amount").sum())
)
Additional Graph Analytics
Shortest Path Analysis
Find shortest paths between nodes:
from polars_grouper import calculate_shortest_path
df = pl.DataFrame({
"from": ["A", "A", "B", "C"],
"to": ["B", "C", "C", "D"],
"weight": [1.0, 2.0, 1.0, 1.5]
})
paths = df.select(
calculate_shortest_path(
pl.col("from"),
pl.col("to"),
pl.col("weight"),
directed=False
).alias("paths")
).unnest("paths")
PageRank Calculation
Calculate node importance:
from polars_grouper import page_rank
df = pl.DataFrame({
"from": ["A", "A", "B", "C", "D"],
"to": ["B", "C", "C", "A", "B"]
})
rankings = df.select(
page_rank(
pl.col("from"),
pl.col("to"),
damping_factor=0.85
).alias("pagerank")
).unnest("pagerank")
Association Rule Mining
Discover item relationships:
from polars_grouper import graph_association_rules
transactions = pl.DataFrame({
"transaction_id": [1, 1, 1, 2, 2, 3],
"item_id": ["A", "B", "C", "B", "D", "A"],
"frequency": [1, 2, 1, 1, 1, 1]
})
rules = transactions.select(
graph_association_rules(
pl.col("transaction_id"),
pl.col("item_id"),
pl.col("frequency"),
min_support=0.1
).alias("rules")
).unnest("rules")
Betweenness Centrality
Identify bridge nodes:
from polars_grouper import betweenness_centrality
df = pl.DataFrame({
"from": ["A", "A", "B", "C", "D", "E"],
"to": ["B", "C", "C", "D", "E", "A"]
})
centrality = df.select(
betweenness_centrality(
pl.col("from"),
pl.col("to"),
normalized=True
).alias("centrality")
).unnest("centrality")
Performance
The library is implemented in Rust for high performance:
- Efficient memory usage
- Fast computation for large graphs
- Seamless integration with Polars' lazy evaluation
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Release files for polars-grouper 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| polars_grouper-0.6.0.tar.gz | 54.9 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| polars_grouper-0.6.0-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details |
| polars_grouper-0.6.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| polars_grouper-0.6.0-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-32 | Details |
| polars_grouper-0.6.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
| polars_grouper-0.6.0-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 25.1 MB
Release files / polars_grouper-0.6.0.tar.gz
| Download URL | polars_grouper-0.6.0.tar.gz |
|---|---|
| Size | 54.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a855d32380c67d55f91300dc60abcf9cd5699248558b781c967f923485480c47
|
|
BLAKE2b-256 checksum How to use checksums |
dded9e4a83822c94eda1debf2265d9a5720db452521bc56ea455d300a3d80c34
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / polars_grouper-0.6.0-cp310-abi3-win_amd64.whl
| Download URL | polars_grouper-0.6.0-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 4.7 MB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
5435d8a8d6b276b2830b65227056c2ec068a47e90e6d41dd74b69d9e44e80deb
|
|
BLAKE2b-256 checksum How to use checksums |
4243c6d1c4c08ab803d3747f86969f58f5a5772d5bf2a988e99ebe39748bd2e2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / polars_grouper-0.6.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | polars_grouper-0.6.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 5.3 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
a27419509237899275326c12019ccdbb67baca12b4fe93bff562c1fedf6135c3
|
|
BLAKE2b-256 checksum How to use checksums |
3224f35944f860d734bb592ae3a3eda45b522bb5ccdd6625961b44b551451bb2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / polars_grouper-0.6.0-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl
| Download URL | polars_grouper-0.6.0-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl |
|---|---|
| Size | 6.1 MB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-32 abi3 |
|
SHA-256 checksum How to use checksums |
47b8d01ebba4b77a2453e7a147dfe374ad6094bf11e759f4a56bf853df00deb9
|
|
BLAKE2b-256 checksum How to use checksums |
81b7ef12b24a6b7819fd7052bf1a604450c6289fb211a200cefb3331c45e438c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / polars_grouper-0.6.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | polars_grouper-0.6.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 4.4 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
6860b9ddae4e594961dd4ddd4100511a47473fb22352c87d601ae342931f6bfd
|
|
BLAKE2b-256 checksum How to use checksums |
8e73429d713be960c4221ce752d73002df9b3d176c86018eb07c7db3d1c3e364
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|
Release files / polars_grouper-0.6.0-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | polars_grouper-0.6.0-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 4.6 MB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
f7db74f46df1a2c1e534abdecd0319f1226e1afdf486967a3fa2ecb240311dbd
|
|
BLAKE2b-256 checksum How to use checksums |
10e61c272c141ced3034a62b116319b7ac2d800949f7d17c58c8e42f08b9e2eb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
maturin/1.15.0
|