Skip to content

Allotaxonometer

The Dodds (2020) diamond allotaxonograph — a rotated-square rank-rank histogram with iso-divergence contours and a wordshift list. Works on any two ranked lists.

keyflux.viz.allotaxonometer.allotaxonometer(list1, list2, *, alpha=1.0 / 3.0, labels=None, top=25, bins=36, n_contours=8, figsize=(13.0, 7.0), cmap='inferno', contour_color='0.75')

Draw the diamond allotaxonograph (Dodds et al. 2020) of two ranked lists.

Parameters:

Name Type Description Default
list1 RankedList

The first ranked list (its types lean to the left half).

required
list2 RankedList

The second ranked list (its types lean to the right half).

required
alpha float

Rank-turbulence-divergence tuning parameter; 0 uses the logarithmic limit. Sets the iso-divergence contours and the wordshift.

1.0 / 3.0
labels tuple[str, str] | None

(label1, label2); falls back to each list's own label, then to generic names.

None
top int

Number of top contributors in the wordshift panel.

25
bins int

Number of bins per axis in the rank-rank histogram.

36
n_contours int

Number of iso-divergence contour lines.

8
figsize tuple[float, float]

Figure size in inches.

(13.0, 7.0)
cmap str

Colormap for the histogram (read under a logarithmic norm).

'inferno'
contour_color str

Colour of the iso-divergence contours.

'0.75'

Returns:

Name Type Description
A Figure

class:matplotlib.figure.Figure with three axes (diamond, colorbar,

Figure

wordshift). The function never calls show().

Raises:

Type Description
ValueError

If either list is empty (propagated from :func:rtd).

Contract
  • Returns a Figure with exactly three axes.
  • Inputs are never mutated.
  • The histogram, contours, and wordshift all derive from the same aligned ranks and rtd call, so they cannot disagree.
  • Exclusive types (present in one list only) sit at a tied-last rank on the outer edges.

Examples:

>>> from keyflux.datasets import load_jkbren_example
>>> r1, r2 = load_jkbren_example()
>>> fig = allotaxonometer(r1, r2, alpha=1.0, labels=("A", "B"))
>>> type(fig).__name__
'Figure'
>>> len(fig.axes)
3
Source code in keyflux/viz/allotaxonometer.py
def allotaxonometer(
    list1: RankedList,
    list2: RankedList,
    *,
    alpha: float = 1.0 / 3.0,
    labels: tuple[str, str] | None = None,
    top: int = 25,
    bins: int = 36,
    n_contours: int = 8,
    figsize: tuple[float, float] = (13.0, 7.0),
    cmap: str = "inferno",
    contour_color: str = "0.75",
) -> Figure:
    """Draw the diamond allotaxonograph (Dodds et al. 2020) of two ranked lists.

    Args:
        list1: The first ranked list (its types lean to the left half).
        list2: The second ranked list (its types lean to the right half).
        alpha: Rank-turbulence-divergence tuning parameter; ``0`` uses the
            logarithmic limit. Sets the iso-divergence contours and the wordshift.
        labels: ``(label1, label2)``; falls back to each list's own label, then to
            generic names.
        top: Number of top contributors in the wordshift panel.
        bins: Number of bins per axis in the rank-rank histogram.
        n_contours: Number of iso-divergence contour lines.
        figsize: Figure size in inches.
        cmap: Colormap for the histogram (read under a logarithmic norm).
        contour_color: Colour of the iso-divergence contours.

    Returns:
        A :class:`matplotlib.figure.Figure` with three axes (diamond, colorbar,
        wordshift). The function never calls ``show()``.

    Raises:
        ValueError: If either list is empty (propagated from :func:`rtd`).

    Contract:
        - Returns a Figure with exactly three axes.
        - Inputs are never mutated.
        - The histogram, contours, and wordshift all derive from the same aligned
          ranks and ``rtd`` call, so they cannot disagree.
        - Exclusive types (present in one list only) sit at a tied-last rank on the
          outer edges.

    Examples:
        >>> from keyflux.datasets import load_jkbren_example
        >>> r1, r2 = load_jkbren_example()
        >>> fig = allotaxonometer(r1, r2, alpha=1.0, labels=("A", "B"))
        >>> type(fig).__name__
        'Figure'
        >>> len(fig.axes)
        3
    """
    if labels is None:
        labels = (list1.label or "system 1", list2.label or "system 2")

    result = rtd(list1, list2, alpha=alpha)
    types, ranks1, ranks2 = list1.aligned(list2)
    x = np.log10(np.asarray(ranks1))
    y = np.log10(np.asarray(ranks2))
    extent = max(float(np.ceil(max(np.max(x), np.max(y)))), 1.0)

    t1, t2 = list1.types(), list2.types()
    n_shared = len(t1 & t2)

    fig = Figure(figsize=figsize)
    gs = fig.add_gridspec(nrows=1, ncols=3, width_ratios=[6.0, 0.25, 3.5], wspace=0.4)
    ax_diamond = fig.add_subplot(gs[0, 0])
    ax_cbar = fig.add_subplot(gs[0, 1])
    ax_shift = fig.add_subplot(gs[0, 2])

    mesh = _diamond_histogram(ax_diamond, x, y, extent, bins=bins, cmap=cmap)
    _iso_divergence_contours(
        ax_diamond, extent, alpha=alpha, n_contours=n_contours, color=contour_color
    )
    _diamond_axes(ax_diamond, extent, labels)
    cbar = fig.colorbar(mesh, cax=ax_cbar)
    cbar.set_label("types per cell", fontsize=9)

    share1, share2 = _wordshift_panel(ax_shift, result, labels, top=top)
    total = share1 + share2
    if total > 0:
        ax_shift.set_title(
            f"balance: {labels[0]} {share1 / total:.0%}  |  "
            f"{labels[1]} {share2 / total:.0%}",
            fontsize=9,
        )
    _annotations(
        fig,
        ax_diamond,
        result,
        labels,
        alpha=alpha,
        n_types=len(types),
        n_shared=n_shared,
        n_excl1=len(t1 - t2),
        n_excl2=len(t2 - t1),
    )
    fig.subplots_adjust(top=0.9)
    return fig