geom_bracket()

In [1]:
import pandas as pd

from lets_plot import *
In [2]:
LetsPlot.setup_html()
In [3]:
def dodged_coord(group_id, subgroup_id, subgroups_count, width=.95):
    median = (subgroups_count - 1) / 2
    offset = (subgroup_id - median) * width
    scaler = 1.0 / subgroups_count
    return group_id + offset * scaler
In [4]:
df = pd.read_csv("https://raw.githubusercontent.com/JetBrains/lets-plot-docs/refs/heads/master/data/mpg.csv")
print(df.shape)
df.head()
(234, 12)
Out[4]:
Unnamed: 0 manufacturer model displ year cyl trans drv cty hwy fl class
0 1 audi a4 1.8 1999 4 auto(l5) f 18 29 p compact
1 2 audi a4 1.8 1999 4 manual(m5) f 21 29 p compact
2 3 audi a4 2.0 2008 4 manual(m6) f 20 31 p compact
3 4 audi a4 2.0 2008 4 auto(av) f 21 30 p compact
4 5 audi a4 2.8 1999 6 auto(l5) f 16 26 p compact
In [5]:
p = ggplot(df, aes("drv", "hwy")) + geom_boxplot()
grouped_p = ggplot(df, aes(x="drv", y="hwy")) + geom_boxplot(aes(fill=as_discrete("year")))

Basic Plot

In [6]:
brackets_data = {
    "xmin": ["f", "f"],
    "xmax": ["4", "r"],
    "y": [46, 48],
    "lower_y": [10, 8],
    "label": [0.01, 0.02],
    "g": ["A", "B"]
}

bracket_plot = p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data)
bracket_plot
Out[6]:

Brackets Between Groups

In [7]:
brackets_grouped_data = {
    "xmin": [-.2375, 0.7625, 1.7625],
    "xmax": [.2375, 1.2375, 2.2375],
    "y": [46, 30, 28],
    "label": [0.01, 0.02, 0.03],
    "g": ["A", "B", "C"],
}

grouped_p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_grouped_data)
Out[7]:

Orientation

In [8]:
ggplot() + \
    geom_boxplot(aes("hwy", "drv"), data=df) + \
    geom_bracket(aes(ymin="xmin", ymax="xmax", x="y", label="label"), data=brackets_data)
Out[8]:

Remove Label

In [9]:
gggrid([
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y"), data=brackets_data),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, size=0),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, alpha=0, segment_alpha=1),
], ncol=2)
Out[9]:

Aesthetics

Own Aesthetics

tip_length_start/tip_length_end

In [10]:
gggrid([
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data) + \
        ggtitle("Default"),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label", tip_length_end=[40, 50]), data=brackets_data) + \
        ggtitle("aes(tip_length_end=...)"),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, tip_length_end=15) + \
        ggtitle("tip_length_end=15"),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="lower_y", label="label"), data=brackets_data, tip_length_start=-5, tip_length_end=-5) + \
        ggtitle("tip_length_xxx=-5"),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="lower_y", label="label"), data=brackets_data, tip_length_start=-5) + \
        ggtitle("tip_length_start=-5"),
], ncol=2)
Out[10]:

Text Aesthetics

In [11]:
def aes_plots(name, const_value, value="g"):
    mapping = dict(xmin="xmin", xmax="xmax", y="y", label="label")
    return [
        p + geom_bracket(aes(**{**mapping, **{name: value}}), data=brackets_data) + ggtitle(f"aes({name}='{value}')"),
        p + geom_bracket(aes(**mapping), data=brackets_data, **{name: const_value}) + ggtitle(f"{name}: {const_value}"),
    ]
In [12]:
gggrid(aes_plots("alpha", .5))
Out[12]:
In [13]:
gggrid(aes_plots("color", "green"))
Out[13]:
In [14]:
gggrid(aes_plots("size", 5))
Out[14]:
In [15]:
aes_plots("family", "courier")[1]
Out[15]:
In [16]:
aes_plots("fontface", 'bold_italic')[1]
Out[16]:
In [17]:
gggrid([
    aes_plots("hjust", 0)[1],
    aes_plots("hjust", .5)[1],
    aes_plots("hjust", 1)[1],
])
Out[17]:
In [18]:
gggrid([
    aes_plots("vjust", 0)[1],
    aes_plots("vjust", .5)[1],
    aes_plots("vjust", 1)[1],
])
Out[18]:
In [19]:
p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="lower_y", label="label"), data=brackets_data,
                 tip_length_start=-5, tip_length_end=-5, vjust=2) + \
    ggtitle("tip_length_xxx=-5, vjust=2")
Out[19]:
In [20]:
aes_plots("angle", 30)[1]
Out[20]:
In [21]:
gggrid([
    ggplot() + geom_bracket(xmin=-1, xmax=1, label="ABC\nDEF") + ggtitle("Default lineheight"),
    ggplot() + geom_bracket(xmin=-1, xmax=1, label="ABC\nDEF", lineheight=2) + ggtitle("lineheight=2"),
]) + ggtitle("Multiline label")
Out[21]:

Segment Aesthetics

In [22]:
gggrid(aes_plots("linetype", 'dotted'))
Out[22]:
In [23]:
gggrid(aes_plots("segment_color", "green"))
Out[23]:
In [24]:
gggrid(aes_plots("segment_size", 4))
Out[24]:
In [25]:
gggrid(aes_plots("segment_alpha", .5))
Out[25]:

Parameters

Own Parameters

bracket_shorten

In [26]:
gggrid([
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data) + ggtitle("Default"),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, bracket_shorten=.75) + ggtitle("bracket_shorten=.75"),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, bracket_shorten=1.25) + ggtitle("bracket_shorten=1.25"),
])
Out[26]:

tip_length_unit

In [27]:
gggrid([
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data) + \
        ggtitle("Default"),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, tip_length_unit='px') + \
        ggtitle("tip_length_unit='px'"),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, tip_length_unit='identity') + \
        ggtitle("tip_length_unit='identity'"),
])
Out[27]:

Text Parameters

label_format

In [28]:
p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, label_format=".2~e")
Out[28]:

na_text

In [29]:
brackets_na_data = {
    "xmin": ["f", "f"],
    "xmax": ["4", "r"],
    "y": [46, 48],
    "label": [0.01, None],
    "g": ["A", "B"]
}

p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_na_data, na_text="None")
Out[29]:

nudge_x/nudge_y, nudge_unit

In [30]:
gggrid([
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data) + ggtitle("Default"),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, nudge_y=-2) + ggtitle("nudge_y=-2"),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, nudge_x=.25) + ggtitle("nudge_x=.25"),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, nudge_x=10, nudge_unit='px') + \
        ggtitle("nudge_x=10, nudge_unit='px'"),
], ncol=2)
Out[30]:

size_unit

In [31]:
gggrid([
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, size=5) + ggtitle("Default size_unit"),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, size=5, size_unit='y') + ggtitle("size_unit='y'"),
]) + ggsize(800, 600)
Out[31]:

Standard Parameters

position

In [32]:
brackets_grouped_data2 = {
    "xmin": ["f", "f", "f", "f"],
    "xmax": ["4", "4", "r", "r"],
    "y": [50, 50, 56, 56],
    "label": [0.01, 0.03, 0.02, 0.04],
    "year": [1999, 2008, 1999, 2008]
}

gggrid([
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, position=position_nudge(x=.25)) + \
        ggtitle("position_nudge(x=.25)"),
    p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, position=position_nudge(y=-2)) + \
        ggtitle("position_nudge(y=-2)"),
    grouped_p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label", color=as_discrete("year")), data=brackets_grouped_data2, position=position_dodgev()),
], ncol=2)
WARN: The function position_dodgev() is deprecated and will be removed in future releases.
Out[32]:

show_legend

In [33]:
p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label",
                     size="g", angle="g", linetype="g", segment_color="g", segment_alpha="g", segment_size="g"),
                 data=brackets_data, show_legend=True)
Out[33]:

manual_key

In [34]:
p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, color="blue", manual_key="p-value")
Out[34]:

sampling

In [35]:
p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=brackets_data, sampling=sampling_random(1))
Out[35]:

color_by

In [36]:
p + geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label", paint_a="g"), data=brackets_data, color_by="paint_a")
Out[36]:

Interaction With Other Layers

Marginal

In [37]:
bracket_plot + ggmarginal("r", layer=geom_histogram())
Out[37]:

Toolbar

In [38]:
bracket_plot + ggtb()
Out[38]:

Facets

In [39]:
grouped_p + \
    geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label", color=as_discrete("year")), data=brackets_grouped_data2) + \
    facet_grid(x="year")
Out[39]:

Coordinate Systems

Flip

In [40]:
bracket_plot + coord_flip()
Out[40]:

Tests

In [41]:
tests = [
    {
        'title': "Empty data",
        'data': {
            'xmin': [],
            'xmax': [],
            'y': [],
            'label': [],
        }
    },
    {
        'title': "One element",
        'data': {
            'xmin': [0],
            'xmax': [1],
            'y': [0],
            'label': ["a"],
        }
    },
    {
        'title': "NaN's in data",
        'data': {
            'xmin': [0, None, 0, 0, 0],
            'xmax': [1, 1, None, 1, 1],
            'y': [0, 1, 2, None, 4],
            'label': ["a", "a", "a", "a", None],
        }
    },
]

gggrid([
    ggplot(t['data']) + \
        geom_bracket(aes(xmin='xmin', xmax='xmax', y='y', label='label')) + \
        ggtitle(t['title'])
    for t in tests
], ncol=2)
Out[41]:
In [42]:
def get_test_plot(dy, shift):
    y_base = shift * dy
    box_data = {
        "x": ["A", "B", "C"],
        "y": [y_base, y_base + dy, y_base - dy],
        "ymin": [y_base - 5 * dy, y_base - 4 * dy, y_base - 6 * dy],
        "ymax": [y_base + 5 * dy, y_base + 6 * dy, y_base + 4 * dy],
    }
    bracket_data = {
        "xmin": ["A", "A"],
        "xmax": ["B", "C"],
        "y": [y_base + 7 * dy, y_base + 8 * dy],
        "label": [0.01, 0.02],
    }
    return ggplot() + \
        geom_crossbar(aes(x="x", y="y", ymin="ymin", ymax="ymax"), data=box_data) + \
        geom_bracket(aes(xmin="xmin", xmax="xmax", y="y", label="label"), data=bracket_data)

gggrid([
    get_test_plot(1e-11, 3.14e6),
    get_test_plot(1e-11, -3.14e6),
    get_test_plot(1e11, 3.14e6),
    get_test_plot(1e11, -3.14e6),
], ncol=2)
Out[42]: