Statistical data visualization library for Python built on matplotlib
—
A declarative, object-oriented interface for creating statistical graphics following the Grammar of Graphics approach. The objects interface provides composable classes that can be combined to build customized visualizations through method chaining and layer composition.
Core Concept: Specify what you want to show rather than how to draw it. Build plots by combining data, marks, statistical transformations, positional adjustments, and scales.
from seaborn.objects import PlotCommon imports for marks, stats, and moves:
from seaborn.objects import (
Plot,
# Marks
Dot, Dots, Line, Lines, Path, Bar, Bars, Area, Band, Text,
# Stats
Stat, Agg, Est, Count, Hist, KDE,
# Moves
Move, Dodge, Jitter, Stack, Shift, Norm,
# Scales
Scale, Continuous, Nominal, Boolean, Temporal
)The main interface for declarative plotting that orchestrates data, marks, stats, and scales.
class Plot:
def __init__(self, *args, data=None, **variables):
"""
Initialize plot with data source and variable mappings.
Parameters:
- data: DataFrame or dict with columnar data
- **variables: mappings from variables to data columns (x=, y=, color= etc.)
"""
def add(self, mark, *transforms, orient=None, legend=True, label=None, data=None, **variables):
"""
Add a layer with mark and optional transforms.
Parameters:
- mark: Mark instance defining visual representation
- *transforms: Stat and/or Move instances for data transformation
- orient: "x" or "y" orientation, inferred if not specified
- legend: whether to include layer in legend
- label: label for legend entry
- data: override data source for this layer
- **variables: additional variable mappings for this layer
Returns:
Plot instance for method chaining
"""
def facet(self, col=None, row=None, order=None, wrap=None):
"""
Create subplots with conditional data subsets.
Parameters:
- col: variable for subplot columns
- row: variable for subplot rows
- order: dict specifying order of facet levels
- wrap: wrap columns after this number
Returns:
Plot instance for method chaining
"""
def pair(self, x=None, y=None, wrap=None, cross=True):
"""
Create subplots by pairing multiple x/y variables.
Parameters:
- x: list of variables for x-axis pairing
- y: list of variables for y-axis pairing
- wrap: wrap columns after this number
- cross: if False, pair variables in order rather than crossing
Returns:
Plot instance for method chaining
"""
def scale(self, **scales):
"""
Specify data-to-visual property mappings.
Parameters:
- x, y: Scale instances for coordinate mappings
- color, fill, linestyle, etc.: Scale instances for aesthetic mappings
Returns:
Plot instance for method chaining
"""
def share(self, **shares):
"""
Control sharing of axis limits/ticks across subplots.
Parameters:
- x, y: True (share all), False (independent), or "col"/"row" (share within)
Returns:
Plot instance for method chaining
"""
def limit(self, **limits):
"""
Control range of visible data.
Parameters:
- x, y: tuple of (min, max) values or single values for symmetric limits
Returns:
Plot instance for method chaining
"""
def label(self, title=None, legend=None, **variables):
"""
Control labels for axes, legends, and subplots.
Parameters:
- title: main plot title
- legend: legend title
- **variables: axis labels (x=, y=, color= etc.)
Returns:
Plot instance for method chaining
"""
def layout(self, size=None, engine=None, extent=None):
"""
Control figure size and layout.
Parameters:
- size: tuple of (width, height) in inches
- engine: layout engine ("constrained" or "tight")
- extent: subplot area as fraction of figure
Returns:
Plot instance for method chaining
"""
def theme(self, config):
"""
Control plot appearance via matplotlib rc parameters.
Parameters:
- config: dict of matplotlib rcParams
Returns:
Plot instance for method chaining
"""
def on(self, target):
"""
Use existing matplotlib figure/axes for drawing.
Parameters:
- target: matplotlib Figure, Axes, or SubFigure
Returns:
Plot instance for method chaining
"""
def save(self, loc, **kwargs):
"""
Compile and save plot to file.
Parameters:
- loc: output filename or path-like object
- **kwargs: additional arguments passed to matplotlib savefig
Returns:
Plot instance for method chaining
"""
def show(self, **kwargs):
"""
Compile and display plot.
Parameters:
- **kwargs: additional arguments passed to matplotlib show
"""Visual representation of data through matplotlib artists.
class Mark:
"""Base class for visual representations of data."""
def _mappable_props(self):
"""Properties that can be mapped from data."""
def _grouping_props(self):
"""Properties used for grouping data."""
class Dot(Mark):
"""A mark suitable for dot plots or scatter plots."""
marker: str = "o"
pointsize: float = 6
stroke: float = 0.75
color: str = "C0"
alpha: float = 1
fill: bool = True
edgecolor: str = "auto"
edgealpha: float = "auto"
edgewidth: float = 0.5
edgestyle: str = "-"
class Dots(Mark):
"""Dot mark optimized for handling overplotting with stroke-defined points."""
marker: str = "o"
pointsize: float = 4
stroke: float = 0.75
color: str = "C0"
alpha: float = 1
fill: bool = True
fillcolor: str = "auto"
fillalpha: float = 0.2
class Line(Mark):
"""Connect data points with lines, sorting along orientation axis."""
color: str = "C0"
alpha: float = 1
linewidth: float = "auto"
linestyle: str = "-"
marker: str = ""
pointsize: float = "auto"
fillcolor: str = "auto"
edgecolor: str = "auto"
edgewidth: float = "auto"
class Lines(Mark):
"""Faster line mark using matplotlib LineCollection for many lines."""
color: str = "C0"
alpha: float = 1
linewidth: float = "auto"
linestyle: str = "-"
class Path(Mark):
"""Connect data points in order they appear without sorting."""
# Same properties as Line
class Range(Lines):
"""Oriented line marks between min/max values."""
# Inherits properties from Lines
class Dash(Lines):
"""Line mark as oriented segments for each datapoint."""
width: float = 0.8
class Bar(Mark):
"""Bar mark drawn between baseline and data values."""
color: str = "C0"
alpha: float = 0.7
fill: bool = True
edgecolor: str = "auto"
edgealpha: float = 1
edgewidth: float = "auto"
edgestyle: str = "-"
width: float = 0.8
baseline: float = 0
class Bars(Mark):
"""Optimized bar mark with defaults for histograms."""
# Similar properties to Bar with histogram-optimized defaults
class Area(Mark):
"""Fill mark drawn from baseline to data values."""
color: str = "C0"
alpha: float = 0.2
fill: bool = True
edgecolor: str = "auto"
edgealpha: float = 1
edgewidth: float = "auto"
edgestyle: str = "-"
baseline: float = 0
class Band(Mark):
"""Fill mark representing interval between min and max values."""
color: str = "C0"
alpha: float = 0.2
fill: bool = True
edgecolor: str = "auto"
edgealpha: float = 1
edgewidth: float = 0
edgestyle: str = "-"
class Text(Mark):
"""Textual mark to annotate or represent data values."""
text: str = ""
color: str = "k"
alpha: float = 1
fontsize: float = "auto"
halign: str = "center"
valign: str = "center_baseline"
offset: float = 4Apply statistical transforms before plotting.
class Stat:
"""Base class for statistical transformations."""
group_by_orient: bool = False
def __call__(self, data, groupby, orient, scales):
"""Apply statistical transform to data subgroups."""
class Agg(Stat):
"""Aggregate data along value axis using given method."""
func: str | callable = "mean"
group_by_orient: bool = True
class Est(Stat):
"""Calculate point estimate and error bar interval."""
func: str | callable = "mean"
errorbar: str | tuple = ("ci", 95)
n_boot: int = 1000
seed: int | None = None
group_by_orient: bool = True
class Count(Stat):
"""Count distinct observations within groups."""
group_by_orient: bool = True
class Hist(Stat):
"""Bin observations, count them, optionally normalize/cumulate."""
stat: str = "count" # "count", "density", "percent", "probability", "frequency"
bins: str | int | list = "auto"
binwidth: float | None = None
binrange: tuple | None = None
common_norm: bool | list = True
common_bins: bool | list = True
cumulative: bool = False
discrete: bool = False
class KDE(Stat):
"""Compute univariate kernel density estimate."""
bw_adjust: float = 1
bw_method: str | float | callable = "scott"
common_norm: bool | list = True
common_grid: bool | list = True
gridsize: int | None = 200
cut: float = 3
cumulative: bool = False
class Perc(Stat):
"""Replace data values with percentile ranks."""
k: int = 100
method: str = "linear"
class PolyFit(Stat):
"""Fit a polynomial regression of given order."""
order: int = 2
gridsize: int = 100Make positional adjustments to reduce overplotting or achieve specific layouts.
class Move:
"""Base class for positional transforms."""
group_by_orient: bool = True
def __call__(self, data, groupby, orient, scales):
"""Apply positional transform and return modified data."""
class Jitter(Move):
"""Random displacement along axes to reduce overplotting."""
width: float = "auto" # Relative to mark width
x: float = 0 # Absolute displacement in x
y: float = 0 # Absolute displacement in y
seed: int | None = None
class Dodge(Move):
"""Displacement/narrowing of overlapping marks along orientation."""
empty: str = "keep" # "keep", "drop", "fill"
gap: float = 0
by: list | None = None
class Stack(Move):
"""Displacement of overlapping bars/areas along value axis."""
# No additional parameters
class Shift(Move):
"""Displacement of all marks with same magnitude/direction."""
x: float = 0
y: float = 0
class Norm(Move):
"""Divisive scaling on value axis after aggregating within groups."""
func: str | callable = "max"
where: str | None = None # Query string for subset
by: list | None = None # Variables for aggregation groups
percent: bool = False
group_by_orient: bool = FalseControl mappings between data values and visual properties.
class Scale:
"""Base class for data-to-visual mappings."""
def __call__(self, data):
"""Transform data through the scale pipeline."""
def tick(self, **kwargs):
"""Configure tick selection."""
def label(self, **kwargs):
"""Configure label formatting."""
class Continuous(Scale):
"""Numeric scale supporting norms and functional transforms."""
values: tuple | str | None = None
trans: str | None = None # "log", "sqrt", "symlog", etc.
def tick(self, locator=None, *, at=None, upto=None, count=None, every=None, between=None, minor=None):
"""Configure tick selection for axis/legend."""
def label(self, formatter=None, *, like=None, base=None, unit=None):
"""Configure tick label appearance."""
class Nominal(Scale):
"""Categorical scale without relative importance/magnitude."""
values: tuple | str | list | dict | None = None
order: list | None = None
def tick(self, locator=None):
"""Configure tick selection."""
def label(self, formatter=None):
"""Configure label formatting."""
class Boolean(Scale):
"""Scale with discrete domain of True/False values."""
values: tuple | list | dict | None = None
def tick(self, locator=None):
"""Configure tick selection."""
def label(self, formatter=None):
"""Configure label formatting."""
class Temporal(Scale):
"""Scale for date/time data."""
# No transforms available for temporal data
def tick(self, locator=None, *, upto=None):
"""Configure tick selection for dates."""
def label(self, formatter=None, *, concise=False):
"""Configure date label formatting."""import seaborn.objects as so
import pandas as pd
# Load data
tips = sns.load_dataset("tips")
# Basic scatter plot
(
so.Plot(tips, x="total_bill", y="tip")
.add(so.Dot())
.show()
)# Scatter plot with regression line
(
so.Plot(tips, x="total_bill", y="tip")
.add(so.Dot(alpha=0.5))
.add(so.Line(), so.PolyFit(order=1))
.label(
title="Bill vs Tip with Regression",
x="Total Bill ($)",
y="Tip ($)"
)
.show()
)# Grouped scatter plot with color mapping
(
so.Plot(tips, x="total_bill", y="tip", color="time")
.add(so.Dot())
.scale(color=so.Nominal(values=["skyblue", "orange"]))
.show()
)# Histogram with custom bins and styling
(
so.Plot(tips, x="total_bill")
.add(so.Bar(), so.Hist(bins=20))
.layout(size=(8, 5))
.label(
title="Distribution of Total Bills",
x="Total Bill ($)",
y="Count"
)
.show()
)# Faceted scatter plots
(
so.Plot(tips, x="total_bill", y="tip")
.add(so.Dot(alpha=0.7))
.facet(col="time", row="smoker")
.label(title="Tips by Time and Smoking Status")
.show()
)# Box plot with jittered points
(
so.Plot(tips, x="day", y="total_bill")
.add(so.Range(color="gray"), so.Est(errorbar="sd"))
.add(so.Dot(alpha=0.3), so.Jitter(width=0.3))
.show()
)# Pair plot using objects interface
numeric_cols = ["total_bill", "tip", "size"]
(
so.Plot(tips[numeric_cols])
.pair(cross=True)
.add(so.Dot(alpha=0.5))
.show()
)# Log-scale plot with custom color palette
(
so.Plot(tips, x="total_bill", y="tip", color="size")
.add(so.Dot())
.scale(
x=so.Continuous(trans="log"),
color=so.Continuous(values=("lightblue", "darkred"))
)
.show()
)# Stacked bar chart with custom colors
(
so.Plot(tips, x="day", color="smoker")
.add(so.Bar(), so.Count(), so.Stack())
.scale(color=so.Nominal(values=["lightcoral", "steelblue"]))
.show()
)# Scatter plot with text labels
(
so.Plot(tips.head(10), x="total_bill", y="tip")
.add(so.Dot())
.add(so.Text(text="size"), text="size", offset=8)
.show()
)The typical workflow follows this pattern:
so.Plot(data, x=..., y=..., **aesthetics).add(Mark(), Stat(), Move()) - can chain multiple.scale(x=Scale(), color=Scale(), ...).facet() or .pair() if needed.label(), .layout(), .theme(), .limit(), .share().show() or .save()Each method returns a new Plot instance, enabling flexible method chaining and reusable plot specifications.
# Data types
DataFrame = pandas.DataFrame
Series = pandas.Series
ArrayLike = numpy.ndarray | list
# Property value types
Mappable = Any # Direct values, data column references, or special mappings
ColorSpec = str | tuple # Color names, hex codes, RGB tuples
MarkerSpec = str # Matplotlib marker codes
LineStyleSpec = str | tuple # Matplotlib linestyle codes
# Scale value specifications
ScaleValues = tuple | str | list | dict | None
TransformSpec = str | tuple[callable, callable] | None
# Layout specifications
SizeSpec = tuple[float, float] # (width, height) in inches
LimitSpec = tuple[float, float] | float # (min, max) or symmetric limitInstall with Tessl CLI
npx tessl i tessl/pypi-seaborn