# How to Create a Single Figure Containing Multiple Pandas Boxplot Visualizations Using Seaborn

> Learn to create multiple pandas boxplot visualizations in a single Seaborn figure using Python. Explore plt subplots pd melt and sns FacetGrid for powerful data visualization.

- Repository: [Michael Waskom/seaborn](https://github.com/mwaskom/seaborn)
- Tags: how-to-guide
- Published: 2026-02-16

---

**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`](https://github.com/mwaskom/seaborn/blob/main/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`](https://github.com/mwaskom/seaborn/blob/main/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.

```python
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`.

```python

# 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`](https://github.com/mwaskom/seaborn/blob/main/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`](https://github.com/mwaskom/seaborn/blob/main/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.

```python
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`](https://github.com/mwaskom/seaborn/blob/main/seaborn/categorical.py), ensuring identical rendering quality and statistical accuracy.

## Summary

- **Use `plt.subplots`** when you need fine-grained control over axis positioning, figure dimensions, or when mixing Seaborn boxplots with other matplotlib visualizations.
- **Use `pd.melt` with a single `sns.boxplot`** to display multiple DataFrame columns as categorical groups on one axis, minimizing memory overhead for large datasets.
- **Use `sns.FacetGrid`** for automatic multi-panel layouts with shared axes and consistent styling, leveraging the grid implementation in [`seaborn/axisgrid.py`](https://github.com/mwaskom/seaborn/blob/main/seaborn/axisgrid.py) at 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`](https://github.com/mwaskom/seaborn/blob/main/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.