Skip to main content

Dice plots for high-dimensional categorical data, with matplotlib and plotly backends

Project description

pydiceplot

PyPI - Version PyPI - Python Version PyPI - License PyPI - Downloads PyPI Downloads GitHub Actions

pydiceplot draws dice plots: grids of die-face icons that encode up to nine categorical variables (one per pip slot) plus optional continuous fill and size mappings. It also ships a refactored domino_plot(...) API for two-contrast feature-by-celltype panels. Both plot types share matplotlib and plotly backends with seaborn-style entry points.

It's the Python sibling of the R package ggdiceplot. The grid geometry and legend stack are ports of kuva's DicePlot, which is itself a port of ggdiceplot::geom_dice — so all three packages produce the same visual layout (with one intentional fix: n=6 is the traditional two-column die face rather than ggdiceplot's transposed two-row layout).

Install

pip install pydiceplot

For development against this repo:

git clone https://github.com/maflot/pydiceplot.git
cd pydiceplot
pixi install
pixi run test     # run the test suite
pixi run example  # regenerates the showcase images under images/
pixi run build    # sdist + wheel in dist/
pixi run precommit

Quick start

Categorical mode — each pip is coloured by its pips value:

import matplotlib.pyplot as plt
import pydiceplot
from pydiceplot import dice_plot
from pydiceplot.plots.backends._dice_utils import (
    get_diceplot_example_data, get_example_cat_c_colors,
)

pydiceplot.set_backend("matplotlib")
data = get_diceplot_example_data(4)
colors = dict(list(get_example_cat_c_colors().items())[:4])

fig, ax = dice_plot(
    data,
    x="CellType", y="Pathway", pips="PathologyVariable",
    pip_colors=colors,
    title="Dice Plot with 4 Pathology Variables",
    figsize=(9, 10),
)
fig.savefig("dice_4.png", dpi=150, bbox_inches="tight")

Per-pip continuous fill + size — mirrors ggdiceplot's geom_dice(aes(dots=..., fill=lfc, size=-log10(q))) (we rename dotspips since the marks on a die are formally called pips):

import numpy as np
from pydiceplot import dice_plot

rng = np.random.default_rng(1)
data = get_diceplot_example_data(4)
data["lfc"] = rng.normal(0, 1.2, len(data))
data["nlq"] = rng.uniform(0.5, 4, len(data))

fig, ax = dice_plot(
    data,
    x="CellType", y="Pathway", pips="PathologyVariable",
    fill="lfc", size="nlq",
    fill_label="Log2FC", size_label="-log10(q)",
    cmap="RdBu_r",
    title="Per-dot continuous",
)

Plotly — same API, returns a plotly.graph_objects.Figure:

pydiceplot.set_backend("plotly")
fig = dice_plot(data, x="CellType", y="Pathway", pips="PathologyVariable",
                fill="lfc", size="nlq", cmap="RdBu_r",
                width=900, height=650)
fig.write_image("dice.png")

Drawing into an existing axes (skips the built-in right-side legend stack so you can compose your own multi-panel figure):

fig, axes = plt.subplots(1, 2, figsize=(14, 6))
dice_plot(data, x="CellType", y="Pathway", pips="PathologyVariable",
          pip_colors=colors, ax=axes[0])
axes[1].plot(range(10))

Domino plots use a matching column-first API. Each tile is a (feature, celltype) pair with exactly two contrast slots:

from pydiceplot import domino_plot
from pydiceplot.plots.backends._domino_utils import get_domino_example_data

data = get_domino_example_data()

fig, ax = domino_plot(
    data,
    "gene", "Cell_Type", "Group",
    features=["GeneA", "GeneB", "GeneC"],
    label="var",
    fill="logFC",
    size="neg_log10_adj_p",
    contrast_order=["Type1", "Type2"],
    contrast_labels=["Type 1", "Type 2"],
    fill_label="Log2FC",
    size_label="-log10(adj p)",
    figsize=(9, 5.5),
)

Modes

dice_plot has three input modes, picked by which arguments you pass:

Mode Trigger What each pip encodes
Categorical pip_colors={label: hex, ...} filled circle in its category colour when present
Per-pip continuous fill="col" and/or size="col" continuous colour and/or size from numeric columns
Per-pip discrete fill="col" + fill_palette={value: hex, ...} colour per discrete fill value; pip slot still comes from pips

The legend stack on the right always includes a position legend showing which pip slot maps to which pips value, plus a colorbar and size legend when continuous mappings are active. That stacking matches ggdiceplot::draw_key semantics.

Sample output

Everything below is produced by example_code/example.py. Regenerate with pixi run example.

Quick tour

4-category dice plot

6-category dice plot (traditional two-column face)

9-category dice plot (fully populated 3×3 face)

Per-dot continuous fill and size

Domino example

The standalone domino example lives in example_code/example_domino.py.

Domino plot example

1-to-1 ports of ggdiceplot's demo plots

Each script in example_code/ reproduces one of the figures from ggdiceplot/demo_output/, loading the original R sample data exported to CSV under example_code/data/.

Oral microbiome — 8 taxa × 5 specimens × 4 diseases, per-pip Log2FC and -log10 q. Mirrors sample_dice_data2 / example2.png.

Oral microbiome

Oral microbiome, fill-only — same data but size is constant and pip_scale=1.0 fills the die face fully. Mirrors example4_fill_only.png.

Oral microbiome — fill only

miRNA × compound × organ, discrete direction — the pip slot selects the organ, the pip colour encodes the regulation direction (Down / Unchanged / Up) via fill_palette. Mirrors sample_dice_miRNA.

miRNA dysregulation direction

ZEBRA Sex DEGs domino plot — 9 genes × 27 cell types × 5 disease contrasts, filtered to PValue < 0.05. Mirrors ZEBRA_domino_example.png.

ZEBRA domino

Creative n=9 example

A fully populated 3×3 die face: nine canonical signaling pathways (Wnt, Notch, Hedgehog, TGF-β, Hippo, PI3K-AKT, MAPK, JAK-STAT, NF-κB) per cell-type × treatment tile. Pip colour = Log2FC, pip size = -log10 q. The synthetic data boosts biologically plausible pathway hits: fibroblasts respond to TGF-β1 via TGF-β, macrophages activate NF-κB / JAK-STAT / MAPK under LPS, intestinal stem cells light up Wnt under WNT3A, and so on.

9 signaling pathways per die face

API

dice_plot(data, x, y, pips, *, ...)

dice_plot(
    data, x, y, pips, *,
    # pip encoding
    pip_colors=None,       # dict {pips value: hex} — categorical colour per pip
    fill=None,             # str — per-pip fill column (continuous or discrete)
    fill_palette=None,     # dict {fill value: hex} — discrete fill lookup
    size=None,             # str — numeric per-pip size column
    # ordering
    x_order=None, y_order=None, pips_order=None,
    # dice geometry
    pip_scale=0.85, tile_size=0.85, grid_lines=False,
    # colour scales
    fill_range=None, size_range=None, cmap="viridis",
    # labels
    title=None, xlabel=None, ylabel=None,
    fill_label=None, size_label=None, pips_label=None,
    # plot target
    ax=None,                  # matplotlib: existing Axes (skips legend stack)
    fig=None,                 # plotly: existing Figure (skips legend stack)
    figsize=None,             # matplotlib: (width_in, height_in)
    width=None, height=None,  # plotly: pixels
    max_pips=9,
)

Returns

  • matplotlib: (Figure, Axes) when we create the figure, just Axes when the caller supplies ax=.
  • plotly: plotly.graph_objects.Figure.

Use the native save/show methods on the return value: fig.savefig(...) / plt.show() for matplotlib, fig.write_image(...) / fig.show() for plotly.

domino_plot(data, feature, celltype, contrast, *, ...)

domino_plot(
    data, feature, celltype, contrast, *,
    features=None,          # optional feature filter; also sets order by default
    label=None,             # optional hover/annotation column
    fill="logFC",           # numeric fill column
    size="neg_log10_adj_p", # numeric size column
    feature_order=None, celltype_order=None,
    contrast_order=None,    # must contain exactly two contrast values
    contrast_labels=None,   # human-readable labels for those two slots
    switch_axis=False,
    fill_range=None, size_range=None, cmap="RdBu_r",
    title=None, xlabel=None, ylabel=None,
    fill_label=None, size_label=None,
    ax=None,                  # matplotlib: existing Axes
    fig=None,                 # plotly: existing Figure
    figsize=None,             # matplotlib: (width_in, height_in)
    width=None, height=None,  # plotly: pixels
)

Returns

  • matplotlib: (Figure, Axes) when we create the figure, just Axes when the caller supplies ax=.
  • plotly: plotly.graph_objects.Figure.

Pip slot layout

The 3×3 pip grid uses natural row-major reading order:

pos 1 (TL)  pos 2 (TM)  pos 3 (TR)
pos 4 (ML)  pos 5 (MM)  pos 6 (MR)
pos 7 (BL)  pos 8 (BM)  pos 9 (BR)

Dice sizes pick from this table (traditional die faces; n=6 is two vertical columns, unlike ggdiceplot::make_offsets which returns the transposed two-row layout — we deliberately diverge here):

n positions visual
1 [5] center
2 [1, 9] diagonal (TL + BR)
3 [1, 5, 9] diagonal + center
4 [1, 3, 7, 9] four corners
5 [1, 3, 5, 7, 9] corners + center
6 [1, 3, 4, 6, 7, 9] two vertical columns
7 [1, 3, 4, 5, 6, 7, 9] 6 + center
8 [1, 2, 3, 4, 6, 7, 8, 9] 3×3 minus center
9 [1, 2, 3, 4, 5, 6, 7, 8, 9] fully populated 3×3

Citation

If you use this package, please cite:

M. Flotho, P. Flotho, A. Keller, "DicePlot: a package for high-dimensional categorical data visualization," Bioinformatics, vol. 42, no. 2, btaf337, 2026.

@article{flotho2026diceplot,
  title   = {DicePlot: a package for high-dimensional categorical data visualization},
  author  = {Flotho, Matthias and Flotho, Philipp and Keller, Andreas},
  journal = {Bioinformatics},
  volume  = {42},
  number  = {2},
  pages   = {btaf337},
  year    = {2026},
  publisher = {Oxford University Press}
}

Related packages

  • ggdiceplot — the R / ggplot2 sibling
  • kuva — a Rust plotting library that ships a dice plot

License

MIT — see LICENSE.

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

pydiceplot-1.0.0.tar.gz (1.4 MB view details)

Uploaded Source

Built Distribution

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

pydiceplot-1.0.0-py3-none-any.whl (30.1 kB view details)

Uploaded Python 3

File details

Details for the file pydiceplot-1.0.0.tar.gz.

File metadata

  • Download URL: pydiceplot-1.0.0.tar.gz
  • Upload date:
  • Size: 1.4 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pydiceplot-1.0.0.tar.gz
Algorithm Hash digest
SHA256 38bea845e3ded27f282b07876333e47aeb481acb01c09bb2534084531c26aca9
MD5 640dd4a58dcdc4a2a5ad57146062ce15
BLAKE2b-256 8ee31750f96ce9eb9d1cf2d3aa4f0bad7539616daa1516f103dd52abcde329d1

See more details on using hashes here.

Provenance

The following attestation bundles were made for pydiceplot-1.0.0.tar.gz:

Publisher: pypi-release.yml on maflot/pydiceplot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pydiceplot-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: pydiceplot-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 30.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for pydiceplot-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 036e49911066c29abe075a9a7b0ecbb38fc9142f2ea819dbe38698b65fd24f36
MD5 990f51e79563f4d40d8e05adb4ba7bde
BLAKE2b-256 d2cd59eaf4b1f44de8470e220e6a3fb22973d9aa049011f8d526acd8d53cce3b

See more details on using hashes here.

Provenance

The following attestation bundles were made for pydiceplot-1.0.0-py3-none-any.whl:

Publisher: pypi-release.yml on maflot/pydiceplot

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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