Skip to main content

🦆🐋 Duwhal

Duwhal Logo

High-Performance Bipartite Interaction Graph Engine — powered by DuckDB.

codecov Python ≥ 3.9 License: MIT


Duwhal treats your data as a bipartite graph — Contexts (orders, sessions, sentences, patients) connected to Entities (products, genes, tokens, games) — and gives you a complete toolkit to mine patterns, generate recommendations, and detect stable communities, all with the speed of DuckDB.


Table of Contents


Why Duwhal?

Most recommendation and pattern-mining libraries either:

  • ❌ Require you to build a matrix first (memory bottleneck), or
  • ❌ Are designed for a single domain (e-commerce only), or
  • ❌ Don't give you an explanation for why an item was recommended.

Duwhal does things differently:

Feature Duwhal
Ingestion format Parquet, CSV, Pandas, Polars, Arrow — zero-copy via DuckDB
Recommendation strategies Rules, ItemCF, Graph Path Integral, Popularity
Explainability Every recommendation includes the path that generated it
Domain agnosticism Retail, Genomics, NLP, Music, Social — all the same API
Community detection Tarjan SCC to find "equilibrium" communities & filter bubbles
Scale 100k+ transactions in seconds, on-disk DuckDB for larger data

Core Concepts

Duwhal models interactions as a bipartite graph:

Context₁ ─── Entity_A
Context₁ ─── Entity_B
Context₂ ─── Entity_A
Context₂ ─── Entity_C

From this, it projects a unipartite co-occurrence graph where edges carry probabilistic weights derived from interaction frequency. Recommendations are then paths through this graph.

This model is universal:

Domain Context Entity
Retail Order Product
Genomics Patient / Sample Gene / Mutation
Music Playlist Song
NLP Sentence / Document Token / Concept
Social User Session Content Item

Installation

pip install duwhal

With uv (recommended):

uv add duwhal

Quick Start

from duwhal import Duwhal
from duwhal.datasets import generate_retail_transactions

df = generate_retail_transactions()

with Duwhal() as db:
    # 1. Load your interactions
    db.load_interactions(df, set_col="order_id", node_col="item_name")

    # 2. Mine Association Rules
    rules = db.association_rules(min_support=0.2, min_confidence=0.5)
    print(rules.to_pandas().head())

    # 3. Recommend based on rules
    recs = db.recommend(["Pasta"], strategy="rules", n=3)
    print(recs.column("recommended_item").to_pylist())
    # → ['Tomato Sauce', 'Parmesan', ...]

    # 4. Or use Graph Path Integral for multi-hop discovery
    recs_graph = db.recommend(["iPhone 15"], strategy="graph", n=3)
    print(recs_graph.to_pandas()[["recommended_item", "reason"]])
    # Shows the discovery path for each recommendation

API Overview

Duwhal (main engine)

The unified entry point for all operations.

from duwhal import Duwhal

db = Duwhal()                          # in-memory (default)
db = Duwhal(database="store.duckdb")  # persistent

Loading Data

# From a DataFrame (Pandas or Polars)
db.load_interactions(df, set_col="order_id", node_col="item_id")

# From a Parquet file (zero-copy via DuckDB)
db.load_interactions("transactions.parquet", set_col="order_id", node_col="item_id")

# With a sort column for sequential mining
db.load_interactions(df, set_col="order_id", node_col="item_id", sort_col="timestamp")

# From an interaction matrix (rows = contexts, columns = items)
db.load_interaction_matrix(matrix_df)

Mining

# Frequent Itemsets
itemsets = db.frequent_itemsets(min_support=0.3)

# Association Rules
rules = db.association_rules(min_support=0.1, min_confidence=0.5, min_lift=1.2)

# Sequential Patterns (requires a timestamp column)
patterns = db.sequential_patterns(timestamp_col="ts", min_support=0.05, max_gap=1)

Recommendation Strategies

Strategy Method Best For
"rules" Association Rules High-confidence, interpretable
"cf" Item Collaborative Filtering Similarity-based ("users who liked X also liked Y")
"graph" Path Integral traversal Multi-hop discovery, sparse data
"popular" Global / windowed popularity Cold-start, trending
"auto" Picks the best available General use
# Train models
db.association_rules(min_support=0.1, min_confidence=0.5)
db.fit_cf(metric="jaccard", min_cooccurrence=2)
db.fit_graph(alpha=0.1)
db.fit_popularity(strategy="global")

# Recommend
recs = db.recommend(["item_a"], strategy="cf", n=5)
recs = db.recommend(["item_a"], strategy="graph", scoring="probability", n=5)

# Score a basket's internal cohesion
score = db.score_basket(["Beer", "Diaper"])  # → float

Sink SCC Detection

Identifies self-sustaining communities — nodes that collectively reinforce each other (Tarjan's algorithm over the probabilistic co-occurrence graph):

sccs = db.find_sink_sccs(min_cooccurrence=5, min_confidence=0.1)
# Returns: node, scc_id, scc_size, is_sink, members

InteractionGraph (graph interface)

A higher-level, node-centric API for graph analysis tasks.

from duwhal import InteractionGraph

with InteractionGraph() as graph:
    graph.load_interactions(df, context_col="user_id", node_col="game_title")
    graph.build_topology(min_interactions=2)

    # Multi-hop proximity ranking from seed nodes
    results = graph.rank_nodes(["Mario"], steps=3, scoring="probability", limit=5)
    # Returns: node, score, steps, reason (path)

    # Detect Filter Bubbles / Equilibrium Communities
    communities = graph.find_equilibrium_communities(min_cooccurrence=5, min_confidence=0.1)

Built-in Datasets

Duwhal ships with synthetic generators for every domain, featuring known ground-truth patterns so you can validate algorithms instantly:

from duwhal.datasets import (
    generate_retail_transactions,    # iPhone → Silicone Case, Pasta → Tomato Sauce
    generate_benchmark_patterns,     # Beer & Diaper (100% co-occurrence), Milk/Bread/Butter
    generate_playlist_data,          # Rock cluster ↔ Jazz cluster with bridge
    generate_genomics_data,          # BRCA1 ↔ TP53 co-mutation signal
    generate_nlp_corpus,             # Tech cluster ↔ Economy cluster with bridge sentence
    generate_filter_bubble_data,     # Retro Gaming sink & Modern FPS sink with transient bridge
    generate_large_scale_data,       # Power-law 100k+ transactions for benchmarking
    generate_3scc_dataset,           # Controlled 3-SCC graph for path-integral research
)

Each generator returns a pd.DataFrame with documented columns and optional seed for reproducibility.


Use Cases

Explore the examples/use_cases/ directory:

Example Domain Key Technique
retail_market_basket.py Retail Association Rules + Sequential Patterns
benchmarking_models.py Any Model comparison: Rules vs CF vs Graph vs Popularity
genomics_trajectories.py Genomics Graph Path Integral over gene co-mutation data
nlp_token_cooccurrence.py NLP Token proximity + sequential n-gram discovery
media_playlist_discovery.py Music Multi-hop cross-genre discovery
ecosystem_equilibrium.py Social Sink SCC detection for filter bubble analysis
evaluation_scaling.py Any Large-scale ingestion + benchmarking on 100k+ rows

Evaluation Toolkit

from duwhal.evaluation import temporal_split, random_split, evaluate_recommendations

# Split interactions temporally (respects time ordering)
train, test = temporal_split(df, test_fraction=0.2, timestamp_col="ts")

# Or randomly
train, test = random_split(df, test_fraction=0.2, seed=42)

# Evaluate recommendations
metrics = evaluate_recommendations(model_recs, ground_truth, k=10)
# Returns: precision@k, recall@k, MAP@k

Architecture

duwhal/
├── api.py                  ← Duwhal: unified engine facade
├── graph.py                ← InteractionGraph: node-centric interface
├── core/
│   ├── connection.py       ← DuckDB connection management
│   └── ingestion.py        ← Multi-format data loading (Parquet, DF, Arrow)
├── mining/
│   ├── frequent_itemsets.py
│   ├── association_rules.py
│   ├── sequences.py        ← Sequential pattern mining
│   └── sink_sccs.py        ← Tarjan SCC + sink identification
├── recommenders/
│   ├── graph.py            ← Path Integral traversal
│   ├── item_cf.py          ← ItemCF (Jaccard / Cosine / Lift)
│   └── popularity.py       ← Global + time-windowed popularity
├── evaluation/
│   ├── metrics.py          ← Precision, Recall, MAP
│   └── splitting.py        ← Temporal and random splits
└── datasets/               ← Synthetic generators for 7 domains

License

MIT © Duwhal Contributors

Metadata

Release files for duwhal 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for duwhal 0.3.1
File Size Uploaded Provenance Actions
duwhal-0.3.1.tar.gz 96.6 kB Without provenance Details for duwhal-0.3.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for duwhal 0.3.1
File Interpreter ABI Platform Actions
duwhal-0.3.1-py3-none-any.whl Python 3 none any Details for duwhal-0.3.1-py3-none-any.whl

Total release size: 170.7 kB

Release files / duwhal-0.3.1.tar.gz

Download URL duwhal-0.3.1.tar.gz
Size 96.6 kB
Tags Source
SHA-256 checksum
How to use checksums
49c34ae0462085b1615bd06a0d304bd03c3cb9d6e5b011e37233386db0834f85
BLAKE2b-256 checksum
How to use checksums
8540f9f17743c7cb3d09ec89322d96d52f2fe10b70bc21dae5726fd298196a93
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / duwhal-0.3.1-py3-none-any.whl

Download URL duwhal-0.3.1-py3-none-any.whl
Size 74.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5eacae7793e231f9e8632f9e747bd144a9648a1d50eb32d87636fd4e34e12900
BLAKE2b-256 checksum
How to use checksums
214e23da0a06e79274d1d5af5b1d962988d19e8d7e7e258e1eb6dc17cdc135bd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for duwhal 0.3.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page