Skip to main content

High-performance DataFrame unifying DuckDB (out-of-core) and Pandas (ML feature engineering)

Project description

HybridFrame

PyPI version Python versions License

The fastest way to work with data in Python

HybridFrame unifies DuckDB (out-of-core, lazy) and Pandas (in-memory, ML) into a single DataFrame API. Zero-copy transitions. Zero configuration. Just fast data.


Quick Start

from hybrid_frame import HybridFrame
import pandas as pd

# Create from Pandas
df = pd.DataFrame({
    "name": ["Alice", "Bob", "Charlie", "Diana", "Eve"],
    "age": [25, 30, 35, 28, 32],
    "salary": [50000, 60000, 70000, 55000, 65000],
    "dept": ["Engineering", "Marketing", "Engineering", "Sales", "Marketing"]
})

hf = HybridFrame.from_pandas(df)

# Lazy DuckDB operations — zero copies
result = (
    hf
    .filter("age > 28")
    .sort_values("salary", ascending=False)
    .select(["name", "salary", "dept"])
)

# Materialize to Pandas for ML
X, y = result.to_ml_ready("salary")

Installation

pip install hybrid-frame

With optional dependencies:

pip install hybrid-frame[arrow]          # Zero-copy Arrow materialization
pip install hybrid-frame[memory-guard]   # OOM protection with psutil
pip install hybrid-frame[ml]             # scikit-learn for ML export
pip install hybrid-frame[all]            # Everything

Updates (v0.3.2)

  • Fixed TypeError in diff() on string columns for newer Pandas versions.

Demo Video

https://github.com/user-attachments/assets/b9724de1-17bc-4dc7-83ab-7371ee37cd05

Why HybridFrame?

Operation Pandas HybridFrame Speedup
Filter 1M rows 19ms 12ms 1.5x
Sort + Head 5 78ms 3ms 26x
GroupBy Sum 13ms 17ms 0.7x
Head 5 0.01ms 0.08ms 0.1x

Benchmark on 1M rows, warm start, full round-trip including .to_pandas()

Where HybridFrame wins:

  • Complex analytical queries (filter, sort, groupby, join)
  • Chained operations (filter → sort → head)
  • Large datasets that don't fit in memory

Where Pandas wins:

  • Simple metadata operations (head, tail, select, rename)
  • ML feature engineering (assign, one-hot encode, apply)

Features

Core Operations

# Filter rows
hf.filter("age > 25")
hf.filter(["age > 25", "salary > 60000"])

# Select columns
hf.select(["name", "age"])

# Sort
hf.sort_values("salary", ascending=False)
hf.sort_values(["dept", "salary"], ascending=[True, False])

# Limit rows
hf.head(10)
hf.tail(10)
hf.limit(100)

Aggregations

# Single aggregation
hf.groupby_agg(["dept"], {"salary": "sum"})

# Multiple aggregations
hf.groupby_agg(["dept"], {"salary": ["sum", "mean", "max"]})

# Supported: sum, mean, min, max, count, nunique, std, var

Joins

# Inner join
hf1.join(hf2, on="id")

# Left join
hf1.join(hf2, on="id", how="left")

# Multi-column join
hf1.join(hf2, on=["id", "date"])

Set Operations

hf1.union(hf2)           # UNION ALL
hf1.union(hf2, all=False) # UNION (distinct)
hf1.intersect(hf2)       # INTERSECT
hf1.except_(hf2)         # EXCEPT

Data Cleaning

# Fill missing values
hf.fillna(0)
hf.fillna({"age": 0, "name": "Unknown"})

# Drop rows with missing values
hf.dropna()
hf.dropna(subset=["age", "salary"])

# Replace values
hf.replace({"old_value": "new_value"})
hf.replace({"col1": {1: 10, 2: 20}})

# Clip values
hf.clip(lower=0, upper=100)
hf.clip(lower={"age": 0}, upper={"age": 150})

Feature Engineering

# Add new columns
hf.assign(salary_k=lambda df: df["salary"] / 1000)

# One-hot encoding
hf.one_hot_encode(["dept"])

# Type casting
hf.astype({"age": "int64", "salary": "float64"})

# Cumulative operations
hf.cumsum()
hf.cumprod()
hf.cummin()
hf.cummax()
hf.diff()

Time Series

# Forward fill
hf.time_series_impute("ffill")

# Backward fill
hf.time_series_impute("bfill")

# With datetime column
hf.time_series_impute("ffill", datetime_col="date")

SQL Passthrough

result = hf.sql("""
    SELECT dept, COUNT(*) as count, AVG(salary) as avg_salary
    FROM self
    WHERE age > 25
    GROUP BY dept
    HAVING COUNT(*) > 1
""")

Streaming & Out-of-Core

# Process in chunks
for chunk in hf.fetch_chunked(batch_size=10000):
    process(chunk)

# Arrow streaming
reader = hf.to_arrow_reader(batch_size=8192)
for batch in reader:
    process_arrow(batch)

# PyTorch DataLoader
dataloader = hf.to_torch_dataloader(target_column="salary", batch_size=32)

File I/O

# Read
hf = HybridFrame.from_csv("data.csv")
hf = HybridFrame.from_parquet("data.parquet")

# Write
hf.write_csv("output.csv")
hf.write_parquet("output.parquet")

# With options
hf.write_csv("output.csv", index=False)

Engine Selection

HybridFrame automatically selects the best engine for each operation:

Operation Engine Why
filter() DuckDB SQL-native, lazy evaluation
select() Pandas Zero-copy metadata
sort_values() DuckDB Efficient top-K with heap
groupby_agg() DuckDB Native aggregation
join() DuckDB Hash join, cross-connection transfer
head() / tail() Pandas Immediate materialization
rename() / drop() Pandas Zero-copy metadata
assign() Pandas ML feature engineering
one_hot_encode() Pandas Direct DataFrame access

Memory Management

# Set memory limit
HybridFrame.set_max_memory_gb(4.0)

# Auto-chunking for large materializations
# (automatically chunks when estimated size > memory limit)

# Force materialization (bypass OOM check)
hf.to_pandas(force=True)

Thread Safety

HybridFrame uses a connection pool for thread-safe DuckDB access:

import threading

def process_chunk(chunk):
    hf = HybridFrame.from_pandas(chunk)
    result = hf.filter("value > 0").to_pandas()

threads = [threading.Thread(target=process_chunk, args=(chunk,)) 
           for chunk in data_chunks]
for t in threads:
    t.start()
for t in threads:
    t.join()

Type Stubs

HybridFrame includes full type stubs for IDE support:

hf: HybridFrame = HybridFrame.from_pandas(df)
result: HybridFrame = hf.filter("age > 25")
df: pd.DataFrame = result.to_pandas()

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License - see LICENSE for details.

Author

mukesh - GitHub

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

hybrid_frame-0.3.3.tar.gz (30.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

hybrid_frame-0.3.3-py3-none-any.whl (27.8 kB view details)

Uploaded Python 3

File details

Details for the file hybrid_frame-0.3.3.tar.gz.

File metadata

  • Download URL: hybrid_frame-0.3.3.tar.gz
  • Upload date:
  • Size: 30.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for hybrid_frame-0.3.3.tar.gz
Algorithm Hash digest
SHA256 99d484a8b5e7adee462029588fc8ab302871bbf8b701b7ac5cf814ff9a4b7d41
MD5 92b0e25013a4bf7bea4be66dfcbfbf15
BLAKE2b-256 dde36b4f8ff4b977a3616bee979a7440adbffc18f2a42d9b47c470166f61615e

See more details on using hashes here.

File details

Details for the file hybrid_frame-0.3.3-py3-none-any.whl.

File metadata

  • Download URL: hybrid_frame-0.3.3-py3-none-any.whl
  • Upload date:
  • Size: 27.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.9.6

File hashes

Hashes for hybrid_frame-0.3.3-py3-none-any.whl
Algorithm Hash digest
SHA256 def399ee403fbb9076cffcb76ea446aa7aa85a2b06e46fb910ff0e42ac4297db
MD5 ad58ad4bc42b40b50ac0c6cb5a315328
BLAKE2b-256 1559b9773db265372704617d41e63696e4abc35db9a61baa8eeb930b386f93c5

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page