How to Create a Single Figure Containing Multiple Pandas Boxplot Visualizations Using Seaborn
Use plt.subplots() for explicit layout control, pd.melt() with a single sns.boxplot() call for side-by-side comparison on one axis, or sns.FacetGrid() for automatic multi-panel layouts when visualizing multiple columns from a pandas DataFrame.
When analyzing distributions across several numeric columns in a pandas DataFrame, you often need to create a single figure containing multiple pandas boxplot visualizations using Seaborn to compare medians, quartiles, and outliers efficiently. The mwaskom/seaborn library provides three distinct architectural approaches for generating multi-panel boxplots, all ultimately delegating to the same underlying _CategoricalPlotter class implemented in seaborn/categorical.py.
Understanding Seaborn's Internal Boxplot Architecture
Seaborn’s public boxplot function is a thin wrapper that instantiates a private _CategoricalPlotter object to handle data reshaping, scale handling, and hue mapping. According to the source code in seaborn/categorical.py, the wrapper begins at line 1597, while the core drawing routine plot_boxes starts at line 591.
The _CategoricalPlotter class centralizes parsing of parameters like x, y, hue, and orient, ensuring consistent categorical ordering and width calculations (default width=.8) across all rendering contexts. When generating multiple boxplots, this design allows the same logic to execute whether you are drawing on a single manually created Axes or across a grid of facets.
Method 1: Manual Subplots with plt.subplots
For full control over figure size, axis sharing, and mixed plot types, create a Figure and Axes array explicitly using plt.subplots, then pass each Axes object to sns.boxplot via the ax parameter.
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
# Sample DataFrame with multiple numeric columns
df = pd.DataFrame({
"A": np.random.randn(100),
"B": np.random.randn(100) + 1,
"C": np.random.randn(100) - 1,
})
fig, axes = plt.subplots(1, 3, figsize=(12, 4), sharey=True)
for ax, col in zip(axes, df.columns):
sns.boxplot(data=df, x=col, ax=ax, width=.8)
ax.set_title(f"Boxplot of {col}")
fig.suptitle("Multiple Boxplots – Manual Subplots")
plt.tight_layout()
plt.show()
Each call to sns.boxplot internally constructs a _CategoricalPlotter instance for the specific Axes, ensuring independent scaling unless sharey=True is specified.
Method 2: Long-Form Data with pd.melt
To display all columns side-by-side on a single axis, convert the wide DataFrame to long format using pd.melt, then map the column names to the categorical x variable and the values to y.
# Convert wide to long format
df_long = df.melt(var_name="variable", value_name="value")
sns.boxplot(data=df_long, x="variable", y="value", width=.8)
plt.title("Boxplots of All Columns on One Axis")
plt.show()
This approach leverages the same plot_boxes routine at line 591 of seaborn/categorical.py, but draws all boxes within a single _CategoricalPlotter invocation, making it memory-efficient for large datasets when you do not need separate axes.
Method 3: FacetGrid for Automatic Multi-Panel Layouts
For automatic grid creation with shared axes, legends, and tidy spacing, use sns.FacetGrid from seaborn/axisgrid.py (class definition starts at line 368). Map the melted DataFrame to columns and use map_dataframe to draw boxplots on each facet.
df_long = df.melt(var_name="variable", value_name="value")
g = sns.FacetGrid(df_long, col="variable", sharey=True, height=4, aspect=0.8)
g.map_dataframe(sns.boxplot, x="variable", y="value", width=.8)
g.set_axis_labels("", "value")
g.set_titles(col_template="{col_name}")
g.fig.suptitle("Boxplots – FacetGrid", y=1.02)
plt.show()
Internally, FacetGrid._attach binds each created Axes to a fresh _CategoricalPlotter instance, while handling sharex and sharey propagation across the grid. This ensures consistent categorical width calculations and axis scaling without manual iteration.
Comparing the Three Approaches
| Approach | Best For | Internal Mechanism |
|---|---|---|
| Manual Subplots | Custom layouts, mixed plot types, precise figure sizing | Explicit Axes creation; boxplot instantiates _CategoricalPlotter per axis |
| Long-Form Melt | Quick side-by-side comparison on one axis, minimal code | Single _CategoricalPlotter call with categorical x mapping |
| FacetGrid | Automatic grids, shared axes, publication-ready spacing | FacetGrid manages Axes array; maps boxplot across facets via map_dataframe |
All three methods ultimately invoke the plot_boxes method at line 591 of seaborn/categorical.py, ensuring identical rendering quality and statistical accuracy.
Summary
- Use
plt.subplotswhen you need fine-grained control over axis positioning, figure dimensions, or when mixing Seaborn boxplots with other matplotlib visualizations. - Use
pd.meltwith a singlesns.boxplotto display multiple DataFrame columns as categorical groups on one axis, minimizing memory overhead for large datasets. - Use
sns.FacetGridfor automatic multi-panel layouts with shared axes and consistent styling, leveraging the grid implementation inseaborn/axisgrid.pyat line 368.
Frequently Asked Questions
What is the difference between using manual subplots and FacetGrid for multiple boxplots?
Manual subplots using plt.subplots give you explicit control over the Figure and Axes objects, allowing custom arrangements, mixed plot types, and precise sizing. FacetGrid, defined in seaborn/axisgrid.py, automates the creation of axis grids, handles axis sharing (sharex, sharey), and manages legends and titles consistently across panels, making it ideal for tidy, repetitive layouts.
How do I share axes across multiple Seaborn boxplots?
When using manual subplots, pass sharex=True or sharey=True to plt.subplots() to synchronize scales across the axis array. In FacetGrid, set sharex and sharey parameters when initializing the grid; the class automatically propagates these settings to each underlying Axes during the _attach phase, ensuring consistent categorical width and numeric scaling.
Can I use the hue parameter when plotting multiple DataFrame columns?
Yes. When using the long-form melt approach, you can add a hue parameter to sns.boxplot to introduce an additional categorical dimension (e.g., a grouping variable). The _CategoricalPlotter class handles hue mapping and legend generation automatically. In FacetGrid, you can map variables to hue within map_dataframe, or use the hue parameter in the grid constructor to create separate colors across facets.
Which method is most memory efficient for large DataFrames?
The long-form melt approach with a single sns.boxplot call is typically most memory efficient because it creates only one _CategoricalPlotter instance and one Axes object. Manual subplots and FacetGrid create separate Axes and plotter instances for each column, increasing memory overhead proportional to the number of panels. However, for most practical dataset sizes, the difference is negligible compared to the DataFrame memory footprint itself.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →