Quickstart Guide

This guide introduces the core concept and components of outset plots, then covers how to create them using the outset.OutsetGrid interface.

The outset.OutsetGrid class provides a FacetGrid-like interface to arrange magnifications of plotted data on an axes grid. With this API, magnification frame positioning is automatically determined based on the structure of dataframe content.

The FacetGrid class also supports manual specification of magnified areas, covered subsequently. This interface broadcasts identical plot content over all grid axes then sets individual axes’ data limits to create magnification panels.

The final sections of the guide cover detail aspect ratio management, layout control, and styling — including how to use outset.inset_outsets to transform an OutsetGrid to render magnification plots as inset axes over the main plot, instead of abreast in a grid with the main plot. In addition to outset.OutsetGrid, lower-level outset.marqueeplot and outset.draw_marquee APIs can be used directly for more bespoke applications.

Table of Contents

[1]:
from matplotlib import cbook as mpl_cbook
from matplotlib import pyplot as plt
import numpy as np
import outset as otst
from outset import mark as otst_mark
from outset import patched as otst_patched
from outset import tweak as otst_tweak
from outset import util as otst_util
import pandas as pd
import seaborn as sns

Some imports and setup.

Taxonomy: Marquees, Outsets, and Insets

[2]:
# --- Data Preparation ---
# Create a DataFrame with sample data defining the extent of marquee elements
plot_data = pd.DataFrame(
    {
        "x_values": [0, 2, 5.8, 6],
        "y_values": [1, 4, 8, 9],
        "group": ["group1", "group1", "group2", "group2"],
    }
)

# --- Plot Initialization ---
# Initialize an OutsetGrid object for creating plots with grouped data.
outset_grid = otst.OutsetGrid(  # API is a la FacetGrid
    data=plot_data,
    x="x_values",
    y="y_values",
    col="group",
    hue="group",
    marqueeplot_kws={"mark_glyph_kws": {"markersize": 15}},
)

# --- Rendering ---
# Draw marquee elements.
outset_grid.marqueeplot()

# --- Annotations ---
# Label plot elements.

# Annotate a callout mark.
outset_grid.source_axes.annotate(
    "callout mark",
    xy=(3, 4.5),
    xytext=(3.5, 3.5),
    horizontalalignment="left",
    arrowprops=dict(arrowstyle="->", lw=1),
)

# Annotate a callout leader.
outset_grid.source_axes.annotate(
    "callout leader",
    xy=(2.5, 3.5),
    xytext=(3.5, 2.5),
    horizontalalignment="left",
    arrowprops=dict(arrowstyle="->", lw=1),
)

# Annotate the frame.
outset_grid.source_axes.annotate(
    "frame",
    xy=(1, 2),
    xytext=(1, 2),
    horizontalalignment="center",
)

# Annotate the marquee.
outset_grid.source_axes.annotate(
    "marquee",
    xy=(1.5, 6),
    xytext=(1.5, 6.5),
    ha="center",
    va="bottom",
    arrowprops=dict(
        arrowstyle="-[, widthB=4.0, lengthB=1.0", lw=2.0, color="k"
    ),
)

# --- Finalization ---
# Customize titles for the source and outset axes.
outset_grid.broadcast_source(lambda ax: ax.set_title("source axes"))
outset_grid.broadcast_outset(lambda ax: ax.set_title("outset axes"))

pass  # sponge up last return value, if any
_images/quickstart_6_0.png

The outset library creates multi-scale data visualizations that composite a “source” plot with magnified subsection views. These excerpted plot elements are termed as “outsets.”

The zoom relationships between a source plot and accompanying outsets is indicated by “marquee” frame annotations. A marquee is composed of (1) a bounding frame and (2) a callout, containing (i) an angled leader that tapers to (ii) an identifying mark.

The figure-level outset.OutsetGrid wrangles orchesration of these figure elements over an axis grid, composed of the source plot beside outset magnified panels. Magnified regions, as well as figure styling and layout, are specified via the OutsetGrid initializer. The OutsetGrid.map_dataframe and OutsetGrid.broadcast methods facilitate population of axes grid content. The OutsetGrid.marqueeplot method, usually called last, dispatches rendering of marquee annotations. The next sections will cover how to initialize and populate OutsetGrid plots in detail.

[3]:
# ... continue working on outset_grid object from previous frame

# --- Inset and Outset Creation ---
# Rearrange outset axes as insets onto the source plots
otst.inset_outsets(outset_grid, insets="NW")

# --- Additional Annotations ---
# Further annotate to label the inset and outset axes.

# Annotate the inset and outset axes with an arrow pointing to them.
outset_grid.source_axes.annotate(
    "inset\noutset\naxes",
    xy=(3.5, 8.7),
    xytext=(3.9, 8.7),
    clip_on=False,
    ha="left",
    va="center",
    arrowprops=dict(
        arrowstyle="-[, widthB=1.5, lengthB=0.5", lw=2.0, color="k"
    ),
)

# Add an arrow pointing upwards without text to indicate a specific area.
outset_grid.source_axes.annotate(
    "",
    xy=(2.5, 10.2),
    xytext=(2.5, 10.21),
    clip_on=False,
    ha="center",
    va="bottom",
    arrowprops=dict(arrowstyle="-[, widthB=10, lengthB=0.5", lw=2.0, color="k"),
    annotation_clip=False,
)

# --- Display ---
# Render and display the figure with all the elements.
# Must call explicitly to draw a second time after render in previous frame.
display(outset_grid.figure)

pass  # sponge up last return value, if any
_images/quickstart_8_0.png

If desired, outset axes can be transformed into insets over the source axes by running an OutsetGrid through the outset.inset_outsets function.

[4]:
# --- Data Preparation ---
# Create a DataFrame with sample data defining the extent of marquee elements
data = pd.DataFrame(
    {
        "x": [0, 0.2, 2.3, 1.8, 0.5, 2, 5.8, 5.87, 5.95, 6, 6],
        "y": [1, 2, 2.9, 3, 1.5, 4, 8, 8.2, 8.6, 9, 10],
        "outset": [
            "group1",
            "group1",
            "group1",
            "group1",
            "group1",
            "group1",
            "group2",
            "group2",
            "group2",
            "group2",
            "group2",
        ],
    }
)

# --- Plot Initialization ---
# Initialize an OutsetGrid object for creating plots with grouped data.
outset_grid = otst.OutsetGrid(
    data=data,
    x="x",
    y="y",
    col="outset",
    hue="outset",
    marqueeplot_kws={"mark_glyph_kws": {"markersize": 15}},
)

# --- Mapping Data ---
# Map filled then outline KDE plot (filled) across all axes
outset_grid.map_dataframe(
    sns.kdeplot,
    x="x",
    y="y",
    alpha=0.2,
    levels=2,
    legend=False,
    fill=True,
    thresh=0.01,
)
outset_grid.map_dataframe(
    sns.kdeplot,
    x="x",
    y="y",
    levels=2,
    legend=False,
    thresh=0.01,
)

# Scatter data on all axes.
outset_grid.map_dataframe(sns.scatterplot, x="x", y="y", legend=False)

# --- Finalization ---
# Set titles for the source and outset axes.
outset_grid.broadcast_source(lambda ax: ax.set_title("source axes"))
outset_grid.broadcast_outset(lambda ax: ax.set_title("outset axes"))

# Add a legend and fix any introduced skew
outset_grid.add_legend()
outset_grid.equalize_aspect()

# --- Annotations ---
# Annotate to indicate a data group on the plot.
outset_grid.source_axes.annotate(
    "data group",
    xy=(1, 6),
    xytext=(1, 6.5),
    ha="center",
    va="bottom",
    arrowprops=dict(
        arrowstyle="-[, widthB=5.0, lengthB=1.0", lw=2.0, color="k"
    ),
)

pass  # sponge up last return value, if any
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
_images/quickstart_10_1.png

Marquee annotations are optional, and can eschewed by simply not calling OutsetGrid.marqueeplot.

Inferred Zoom Areas

The outset.OutsetGrid class closely tracks the design and API conventions of seaborn.FacetGrid. You can think of an OutsetGrid as a FacetGrid with an unfaceted (i.e., all data) plot tacked on the front and some additional mechanisms for control of axes data limits.

[5]:
# --- Plot Initialization ---
# Initialize an OutsetGrid object for creating a series of scatter plots.
# The plots will be based on the 'table' and 'depth' features, categorized by
# the 'cut' of the diamonds.
og = otst.OutsetGrid(
    data=sns.load_dataset("diamonds").dropna(),
    aspect=1.5,  # Set aspect ratio
    x="table",
    y="depth",
    col="cut",
    col_order=["Premium"],  # Focus on 'Premium' cut diamonds only
    marqueeplot_kws={
        "mark_glyph": otst_mark.MarkRomanBadges,  # Use Roman badge markers
        "leader_stretch": 0.2,
        "leader_stretch_unit": "inches",
        "leader_tweak": otst_tweak.TweakReflect(horizontal=True),
    },
)

# --- Draw Data ---
# Map scatter plot over all axes.
og.map_dataframe(
    otst_patched.scatterplot,
    x="table",
    y="depth",
    legend=False,  # No individual legend for plots
    zorder=-2,
    color="gray",
    s=otst_util.SplitKwarg(  # pass different args to source vs. outset axes
        source=10,  # Size for source markers
        outset=10,  # Size for outset markers
    ),
)

# --- Finalization ---
# Set a title and add a legend
og.source_axes.set_title("cut = All")

# Render marquee elements
og.marqueeplot()

pass  # sponge up last return value, if any
_images/quickstart_14_0.png

Here, OutsetGrid(col="cut", col_order=["Premium"]) have been specified. This indicates that the dataframe subset where df["cut"] == "Premium" should be plotted as an outset. Then, OutsetGrid.map_dataframe(sns.scatterplot) is called to render data — all data on the source plot and “Premium” cut data on the outset. Finally, OutsetGrid.marqueeplot adds marquee annotations and finalizes axes data limits.

[6]:
# --- Plot Initialization ---
# Initialize an OutsetGrid object to create plots with grouped data.
# The plots will be based on 'table' and 'depth' features, categorized by 'cut'.
outset_grid = otst.OutsetGrid(
    data=sns.load_dataset("diamonds").dropna(),
    aspect=1.5,
    x="table",
    y="depth",
    col="cut",
    col_order=["Fair", "Premium"],  # Focus on 'Fair' and 'Premium' cuts only.
    hue="cut",
    hue_order=["Fair", "Premium"],
    marqueeplot_kws={
        "mark_glyph": otst_mark.MarkRomanBadges,  # Roman numeral callout IDs
        "leader_stretch": 0.2,
        "leader_stretch_unit": "inches",
        "leader_tweak": otst_tweak.TweakReflect(vertical=True),
    },
)

# --- Mapping Plotter across Faceted Data ---
# Map a scatter plot over all axes to visualize the diamond 'table' vs 'depth'.
outset_grid.map_dataframe(
    otst_patched.scatterplot,
    x="table",
    y="depth",
    legend=False,  # Disable individual legend for plots.
    zorder=-2,  # Ensure the scatter plot is behind other plot elements.
    s=30,  # Set marker size.
)

# --- Finalization ---
# Draw marquee elements.
outset_grid.marqueeplot()

# Add a legend to the plot to make it informative.
outset_grid.add_legend()

# Equalize the aspect ratio of the plots to correct any skew introduced.
outset_grid.equalize_aspect()

pass  # sponge up last return value, if any
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
_images/quickstart_16_1.png

Passing the same key OutsetGrid(col="cut", hue="cut") will cause marquee annotations in each axes to render in successive palette colors. Note that hue mapping can be explicitly suppressed by passing hue=False.

[7]:
# --- Data Preparation ---
# Create a DataFrame with sample data defining the extent of marquee elements
data = sns.load_dataset("diamonds").dropna()
filtered_data = data[
    (data["color"] == "E") & (data["cut"].isin(["Fair", "Very Good"]))
]

# --- Plot Initialization ---
# Initialize an OutsetGrid object to create plots with grouped data.
# The plots will be based on 'carat' and 'price' features, categorized by 'cut'
# and 'clarity'.
outset_grid = otst.OutsetGrid(
    data=filtered_data,
    aspect=1.5,
    x="carat",
    y="price",
    col="cut",
    col_order=["Fair", "Very Good"],
    hue="clarity",
    hue_order=["I1", "VVS2"],
    marqueeplot_kws={
        "mark_glyph": otst.mark.MarkRomanBadges,  # roman numeral identifiers
        "leader_stretch": 0.2,
        "leader_stretch_unit": "inches",
        "leader_tweak": otst.tweak.TweakReflect(vertical=True),
    },
)

# --- Mapping Plotter across Faceted Data ---
# Map a scatter plot over all axes to visualize the diamond 'carat' vs 'price'.
outset_grid.map_dataframe(
    otst.patched.scatterplot,  # patch is for mwaskom/seaborn issues #3601
    x="carat",
    y="price",
    legend=False,
    zorder=-2,
    s=30,  # marker size
)

# --- Finalization ---
# Draw marquee elements.
outset_grid.marqueeplot()

# Add a legend to the plot to make it informative.
outset_grid.add_legend()

# Equalize the aspect ratio of the plots to correct any skew introduced.
outset_grid.equalize_aspect()

pass  # sponge up last return value, if any
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
_images/quickstart_18_1.png

Passing different keys OutsetGrid(col="cut", hue="clarity") will facet on both keys, rendering several hue-mapped marquees per outset frame.

Explicit Zoom Areas

[8]:
# adapted from https://seaborn.pydata.org/examples/wide_data_lineplot.html

# --- Data Preparation ---
# Create a random state and generate a DataFrame with cumulative sum values.
values = np.random.RandomState(365).randn(365, 4).cumsum(axis=0)
dates = np.array(range(365))
data = pd.DataFrame(values, dates, columns=["A", "B", "C", "D"])
data = data.rolling(7).mean()  # Apply a rolling mean with a 7-day window.

# --- Plot Initialization ---
# Initialize an OutsetGrid object with explicit marquee position
outset_grid = otst.OutsetGrid(
    aspect=2,
    data=[(210, 6, 250, 12)],  # Explicit marquee positioning.
    col_wrap=1,  # Ensure vertical layout (two rows)
    x="days",
    y="profit",
)

# --- Broadcast Plotter ---
# Broadcast a line plot over all axes..
outset_grid.broadcast(
    sns.lineplot,
    data=data,
    linewidth=2.5,
    zorder=-1,  # Ensure the line plot is behind other plot elements.
)

# --- Finalization ---
# Draw marquee elements and conclude the script.
outset_grid.marqueeplot()

pass  # sponge up last return value, if any
_images/quickstart_21_0.png

To manually specify zoom frames instead of inferring them from data structure, pass frame coordinates instead of a dataframe when initializing OutsetGrid. In this case, outset.OutsetGrid(data=[(210, 6, 250, 12)]).

Because data is not faceted over plot components, we will need to provide data when rendering plot elements. To manually pass a data kwarg through to the plotting function, use OutsetGrid.broadcast instead of OutsetGrid.map_dataframe. In this case, OutsetGrid.broadcast(sns.lineplot, data=df). The plotter will be called with the same data on each axes, then the subsequent call to marqueeplot will adjust axes data limits to magnify areas specified at OutsetGrid initialization in outset axes.

[9]:
# adapted from https://seaborn.pydata.org/examples/wide_data_lineplot.html

# --- Data Preparation ---
# Create a random state and generate a DataFrame with cumulative sum values.
values = np.random.RandomState(365).randn(365, 4).cumsum(axis=0)
dates = np.array(range(365))
data = pd.DataFrame(values, dates, columns=["A", "B", "C", "D"])
data = data.rolling(7).mean()  # Apply a rolling mean with a 7-day window.

# --- Plot Initialization ---
# Initialize an OutsetGrid object to create line and scatter plots.
outset_grid = otst.OutsetGrid(
    aspect=2,
    data=[(210, 6, 250, 12)],  # explicit marquee positioning
    col_wrap=1,  # Ensure vertical layout (two rows)
    x="days",
    y="profit",
)

# --- Broadcast Plotter ---
# Broadcast a line plot over all axes .
outset_grid.broadcast(
    sns.lineplot,
    data=data,
    linewidth=otst.util.SplitKwarg(source=2.5, outset=10),  # Line width.
    zorder=-1,  # Ensure the line plot is behind other plot elements.
)

# Broadcast a scatter plot to highlight specific data points.
outset_grid.broadcast_source(
    sns.scatterplot,
    data=data[::20],  # Data points to highlight.
    zorder=-1,  # Ensure the scatter plot is behind other plot elements.
    s=50,  # Marker size.
)

# --- Finalization ---
# Apply additional plot settings and draw marquee elements.
outset_grid.broadcast_outset(lambda: plt.axis("off"))  # Turn off axis.
outset_grid.marqueeplot()  # Draw marquee elements.

pass  # sponge up last return value, if any
_images/quickstart_23_0.png

Source axes and outset axes can be targeted in isolation using OutsetGrid.broadcast_source and OutsetGrid.broadcast_outset methods. These work equivalently to OutsetGrid.broadcast, except only dispatch the plotter to the source or outset axes, respectively. For scenarios where an individual kwarg’s value should differ between source and outset axes, OutsetGrid.broadcast may be used in conjuction with the outset.util.SplitKwarg utility. In the example above, for instance, SplitKwarg is used to differentiate line width between source and outset axes, OutsetGrid.broadcast(sns.lineplot, linewidth=otst.util.SplitKwarg(source=2.5, outset=10)).

(Equivalent OutsetGrid.map_dataframe_source and OutsetGrid.map_dataframe_outset methods are also available for the data-driven OutsetGrid interface, and OutsetGrid.map_dataframe is compatible with SplitKwarg.)

Aspect Ratio

As much fun as we might have with narrow and/or wide Grace Hopper…

[10]:
# --- Data Preparation ---
# Load the sample image of Grace Hopper.
with mpl_cbook.get_sample_data("grace_hopper.jpg") as image_file:
    image = plt.imread(image_file)

# --- Plot Initialization ---
# Initialize a subplot with 1 row and 2 columns.
fig, axs = plt.subplots(1, 2)

# --- Image Display with Bad Aspects ---
# Display the image with an exaggerated aspect ratio.
axs[0].imshow(image, aspect=7, extent=(0, 1, 0, 1), origin="upper", zorder=-1)
axs[0].set_axis_off()  # Remove the axis for a cleaner look.

# Display the image with a compressed aspect ratio.
axs[1].imshow(image, aspect=0.3, extent=(0, 1, 0, 1), origin="upper", zorder=-1)
axs[1].set_axis_off()  # Remove the axis for a cleaner look.

pass  # sponge up last return value, if any
_images/quickstart_27_0.png

… usually we want to make sure images keep their natural aspect ratio. Luckily, OutsetGrid can take care of that for us.

[11]:
# adapted from https://seaborn.pydata.org/examples/scatter_bubbles.html

# --- Data Preparation ---
# Load the 'mpg' dataset and remove any missing values.
data = sns.load_dataset("mpg").dropna()

# --- Plot Initialization ---
# Initialize an OutsetGrid object to create a scatter plot with bubble sizes.
outset_grid = otst.OutsetGrid(
    data=[(73.5, 23.5, 78.5, 31.5)],  # Explicit marquee positioning.
    color=sns.color_palette()[-1],  # Set color for the marquee.
    marqueeplot_kws={
        "mark_glyph": otst.mark.MarkAlphabeticalBadges(
            start="A",  # Uppercase alphabetical identifiers.
        ),
        "frame_outer_pad": 0.2,
        "frame_outer_pad_unit": "inches",
        "frame_face_kws": {"facecolor": "none"},  # Transparent frame.
    },
)

# --- Broadcast Plotter ---
# Broadcast a scatter plot to visualize 'horsepower' vs 'mpg' with bubble sizes representing 'weight'.
outset_grid.broadcast(
    sns.scatterplot,
    data=data,
    x="horsepower",
    y="mpg",
    hue="origin",  # Color by 'origin'.
    size="weight",  # Size bubbles by 'weight'.
    sizes=(40, 400),  # Range of bubble sizes.
    alpha=0.5,  # Transparency of bubbles.
    palette="muted",  # Color palette.
    zorder=0,  # Z-order for layering.
)

# --- Finalization ---
# Draw marquee elements and set plot titles.
outset_grid.marqueeplot(equalize_aspect=False)
outset_grid.add_legend()
outset_grid.set_titles("")

pass  # sponge up last return value, if any
_images/quickstart_29_0.png

Because synchronized aspect ratios benefit intuitive comparison between outsets and the source plot, under default settings OutsetGrid operations automatically apply corrections to provide equivalent aspect ratios across the source and outset plots. Correction is performed for all plot types, not just images.

The OutsetGrid map dataframe, broadcast, and insetting methods equalize aspect ratio among axes, if necessary, before returning. If you perform other operations that might distort aspect ratios (e.g., adding a legend, stripping axes ticks, etc.) you should call OutsetGrid.equalize_aspect directly at the end of plotting to re-sync aspect ratios

In cases where mismatching aspect ratios are desired — for example, to allow for “better” zoom when aspect is narrow — use the OutsetGrid.marqueeplot(equalize_aspect=False) kwarg.

[12]:
# --- Data Preparation ---
# Load the sample image of Grace Hopper.
with mpl_cbook.get_sample_data("grace_hopper.jpg") as image_file:
    image = plt.imread(image_file)

h, w = image.shape[0], image.shape[1]

# --- Plot Initialization ---
# Initialize an OutsetGrid object for displaying the image with specific marquee frames.
outset_grid = otst.OutsetGrid(
    data=otst.util.NamedFrames(
        {
            # Coordinates for the 'hat' marquee.
            "hat": (0.42 * w, 0.78 * h, 0.62 * w, 0.98 * h),
            # Coordinates for the 'badge' marquee.
            "badge": (0.10 * w, 0.14 * h, 0.40 * w, 0.21 * h),
        }
    ),
    col="swag",  # Name for column mapping (used in titles)
    hue="swag",  # Name for Hue mapping (used in legends)
    aspect=w / h,  # match image aspect
)

# --- Image Display ---
# Turn off the axis and display the image with specified z-order and origin.
outset_grid.broadcast(lambda: plt.axis("off"))
outset_grid.broadcast(
    plt.imshow, image, extent=(0, w, 0, h), origin="upper", zorder=-1
)

# --- Finalization ---
# Draw marquee elements and set the title for the source axes.
outset_grid.source_axes.set_title("The Hopster", loc="left")
outset_grid.marqueeplot(preserve_aspect=True)

pass  # sponge up last return value, if any
_images/quickstart_31_0.png

Default aspect ratio synchronization among axes will ensure aspect ratios match, but original aspect ratios may not be preserved under map, broadcast, and insetting methods. To keep a specific aspect ratio, like you might want when working with an image, pass the OutsetGrid(preserve_aspect=True) kwarg.

Layout Control

[13]:
# --- Plot Initialization ---
# Initialize an OutsetGrid object to create KDE plots for the 'iris' dataset.
outset_grid = otst.OutsetGrid(
    data=sns.load_dataset("iris").dropna(),
    x="petal_width",
    y="petal_length",
    col="species",  # Separate plots by species.
    col_wrap=2,  # Display plots in 2 columns, multiple rows
    color=sns.color_palette()[1],  # Set color for the marquee.
    marqueeplot_kws={
        "mark_glyph": otst.mark.MarkAlphabeticalBadges,
    },  # Alphabetical identifiers, lowercase,
    marqueeplot_source_kws={  # Reorient marquee annotations on source axes
        "leader_tweak": otst.tweak.TweakReflect(),
    },
    marqueeplot_outset_kws={  # Reorient marquee annotations on outset axes
        "leader_tweak": otst.tweak.TweakReflect(vertical=True),
    },
)

# --- Map KDE Plot over faceted data ---
# Map KDE plots separately for source and outset data.
outset_grid.map_dataframe_source(
    sns.kdeplot, x="petal_width", y="petal_length", legend=False, zorder=0
)
outset_grid.map_dataframe_outset(
    sns.kdeplot,
    x="petal_width",
    y="petal_length",
    fill=True,
    legend=False,
    zorder=0,
)

# --- Finalization ---
# Draw marquee elements.
outset_grid.marqueeplot()

pass  # sponge up last return value, if any
_images/quickstart_34_0.png

Marquee styling is controlled by passing content via the OutsetGrid.__init__(marqueeplot_kws=dict()) kwarg.

Notable options include marqueeplot_kws=dict(mark_glyph=outset.mark.MarkAlphabeticalBadges) to switch from numerical marquee identifiers to alphabetical identifiers. Use marqueeplot_kws=dict(leader_tweak=outset.tweak.TweakReflect(horizontal=True)) to flip the orientation of marque identifiers. (The TweakReflect functor also accepts a vertical kwarg.)

[14]:
# --- Plot Initialization ---
# Initialize an OutsetGrid object to create scatter plots for the 'penguins' dataset.
outset_grid = otst.OutsetGrid(
    data=sns.load_dataset("penguins").dropna(),
    x="bill_length_mm",
    y="bill_depth_mm",
    col="island",  # Separate plots by island.
    col_order=["Biscoe", "Dream"],  # Order of islands to display.
    hue="species",  # Color by species.
    marqueeplot_kws={
        "mark_glyph": otst.mark.MarkRomanBadges
    },  # Roman numeral identifiers.
    marqueeplot_source_kws={
        "leader_face_kws": {"alpha": 0.2},
        # Push marquee identifiers apart from crowded area
        "leader_tweak": otst.tweak.TweakSpreadArea(
            spread_factor=(2.6, 2.5),
            xlim=(45.5, 52),
            ylim=(21, 24),
        ),
    },
)

# --- Map Scatter Plot over faceted data ---
# Map scatter plots for 'bill_length_mm' vs 'bill_depth_mm'.
outset_grid.map_dataframe(
    sns.scatterplot, x="bill_length_mm", y="bill_depth_mm", legend=False
)

# --- Finalization ---
# Draw marquee elements, set axis labels, and add a legend.
outset_grid.marqueeplot()
outset_grid.set_axis_labels("bill length (mm)", "bill depth (mm)")
outset_grid.add_legend()

pass  # sponge up last return value, if any
No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
_images/quickstart_36_1.png

The TweakSpreadArea functor is available to resolve collisions between marquee identifying marks by spreading marks within a designated area apart. In this case,

OutsetGrid(
    marqueeplot_source_kws={
        "leader_tweak": otst.tweak.TweakSpreadArea(
            spread_factor=(2.6, 2.5),
            xlim=(45.5, 52),
            ylim=(21, 24),
        ),
    },
)

pushes the i and iii markers apart.

See documentation for outset.marqueeplot, outset.draw_marquee, or the gallery page to see additional available styling and layout options.

[15]:
# --- Plot Initialization ---
# Initialize an OutsetGrid object to create KDE plots for the 'iris' dataset.
outset_grid = otst.OutsetGrid(
    data=sns.load_dataset("iris").dropna(),
    x="petal_width",
    y="petal_length",
    col="species",  # Separate plots by species.
    color=sns.color_palette()[1],  # Set color for the marquee.
    marqueeplot_kws={
        "mark_glyph": otst.mark.MarkAlphabeticalBadges,
    },  # Alphabetical identifiers, lowercase,
    marqueeplot_source_kws={  # Reorient marquee annotations on source axes
        "leader_tweak": otst.tweak.TweakReflect(),
    },
    marqueeplot_outset_kws={  # Reorient marquee annotations on outset axes
        "leader_tweak": otst.tweak.TweakReflect(vertical=True),
    },
)

# --- Map KDE Plot over faceted data ---
# Map KDE plots separately for source and outset data.
outset_grid.map_dataframe_source(
    sns.kdeplot, x="petal_width", y="petal_length", legend=False, zorder=0
)
outset_grid.map_dataframe_outset(
    sns.kdeplot,
    x="petal_width",
    y="petal_length",
    fill=True,
    legend=False,
    zorder=0,
)

# --- Finalization ---
otst.inset_outsets(outset_grid, "SE")  # move outsets into lower right of source
outset_grid.marqueeplot()  # Draw marquee elements.

pass  # sponge up last return value, if any
_images/quickstart_38_0.png

Any OutsetGrid can be transformed into an inset plot through outset.inset_outsets. This function takes an OutsetGrid as an argument, and rearranges it to reposition outset axes directly over the source plot axes. The corner to place outset axes in can be designated as NE (upper right), SE (lower right), etc., like outset.inset_outsets(outset_grid, "SE").

Several levels of abstraction are available to decide inset geometry — a call to outset.util.layout_corner_insets or even hard axes-relative coordinates can be provided in place of a cardinal corner designation. See the gallery for examples.

Axes-level Interfaces

Most use cases should only need OutsetGrid, but outset does make more granular control available through lower-level APIs.

[16]:
# --- Plot Initialization ---
# Initialize a subplot.
fig, ax = plt.subplots(1)

# --- Marquee Plotting ---
# Create a marquee plot with the 'iris' dataset.
otst.marqueeplot(
    data=sns.load_dataset("iris"),
    x="petal_length",
    y="petal_width",
    hue="species",  # Color by species.
    ax=ax,  # Specify the axis for plotting.
    leader_stretch=0.4,  # Stretch factor for leaders.
    leader_tweak=otst.tweak.TweakReflect(
        vertical=True
    ),  # Tweak leaders for better visibility.
)

# --- Scatter Plot ---
# Overlay a scatter plot on the same axes.
sns.scatterplot(
    data=sns.load_dataset("iris"),
    x="petal_length",
    y="petal_width",
    hue="species",  # Color by species.
    ax=ax,  # Specify the axis for plotting.
)

pass  # sponge up last return value, if any
_images/quickstart_42_0.png

Use marqueeplot for seaborn-like, data-oriented application of marquee annotations to a single Axes. (This function determines frame positions according to the x/y extents of data subsets.)

[17]:
# adapted from https://seaborn.pydata.org/examples/smooth_bivariate_kde.html

# --- Plot Initialization ---
# Initialize a subplot.
fig, ax = plt.subplots(1)

# --- KDE Plotting ---
# Create a KDE plot of body mass vs. bill depth for the penguins dataset.
sns.kdeplot(
    data=sns.load_dataset("penguins"),
    x="body_mass_g",
    y="bill_depth_mm",
    ax=ax,
    fill=True,
    clip=((2200, 6800), (10, 25)),  # Limit the data to a specific range.
    thresh=0,
    levels=100,  # Set the number of contour levels.
    cmap="rocket",  # Color map.
)

# --- Marquee Annotation ---
# Draw a  single marquee around a specific region of interest.
otst.draw_marquee(
    (5200, 6000),  # X range for the marquee.
    (14, 16),  # Y range for the marquee.
    ax,
    color="teal",  # Color of the marquee.
    mark_glyph=otst.mark.MarkArrow(rotate_angle=-45),  # Marquee identifier
    leader_stretch_unit="inches",
    leader_stretch=0.2,  # Stretch factor for leaders.
    zorder=10,
)

# --- Annotation ---
# Add a fun text annotation.
ax.annotate("zoink?", (6300, 17), color="teal", zorder=5)

pass  # sponge up last return value, if any
_images/quickstart_44_0.png

Use draw_marquee to manually add individual annotations.

Further Subjects

The class OutsetGrid inherits from seaborn.FacetGrid, which provides a rich set of initializer kwargs and member properties/functions available to the end user. See the README and examples in the gallery for examples demonstrating these customization capabilities, as well as the full range of styling customization capabilities available.

Some caveats and known limitations should be noted.

  • Try to call OutsetGrid.marqueeplot later rather than earlier — the layout algorithms make use of information about current axes state to determine positioning and sizing of annotation elements.

  • OutsetGrid construction can sometimes be sensitive to operation order, especially when generating colorbars and legends. Again, refer to gallery for examples with working operation orders.

  • Inverted axes are currently not supported.

  • See here for an example involving log-scale axes.

Finally, if this code is useful to you, please consider leaving a ⭐ on GitHub. Thanks!