HOWTO · Seaborn

How to Create a Seaborn Box Plot in Python

Create Seaborn box plots in Python, compare categories, show individual observations, and customize the plot.

On this page

Use seaborn.boxplot() to summarize a numeric distribution or compare values across categories. Map your data columns explicitly so Seaborn knows which variable belongs on each axis. A box plot shows the median and interquartile range (IQR); with the default whisker rule, whiskers reach the most extreme observations within 1.5 IQR of the box, and more distant observations appear as individual points.

Annotated box plot diagram naming the parts of the plot: the box spans the lower quartile to the upper quartile with the median line inside, the whiskers reach the lower and upper extremes, and a value beyond the whiskers appears as a single outlier point.

Every example in this article uses the same parts: the box covers the middle half of the data, the line inside it is the median, the whiskers mark the range under the selected rule, and any points beyond the whiskers are plotted individually.

Create a Box Plot for One Distribution

Put the measurements in a DataFrame column and map that column to y. This makes the data role clear and gives the plot a useful axis label. The example below uses fixed values, so the same input is used each time you run it.

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

data = pd.DataFrame({"Score": [12, 14, 15, 16, 18, 19, 21, 24]})
ax = sns.boxplot(data=data, y="Score")
ax.set_title("Score distribution")
plt.show()

Seaborn box plot of the eight Score values: a single vertical box with the median line inside it and whiskers reaching the lowest and highest scores.

The line inside the box marks the median. The box spans the first through third quartiles, so its height represents the middle half of the observations. A longer box indicates greater spread in that central portion of the data. The whiskers show the range under the selected whisker rule; they do not necessarily reach the minimum and maximum values.

Compare Distributions Across Categories

Map a category to x and the numeric measurement to y to place a separate box at each category. In this dataset, each box summarizes Price for one Quantity level. Adding hue="Product" divides each category into product groups, which helps compare products at the same quantity.

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

data = pd.DataFrame(
    {
        "Quantity": [5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8],
        "Price": [9, 10, 15, 16, 13, 14, 15, 18, 11, 12, 14, 15, 16, 17, 18, 19],
        "Product": ["A", "A", "A", "A", "B", "B", "B", "B"] * 2,
    }
)
ax = sns.boxplot(data=data, x="Quantity", y="Price", hue="Product")
ax.set_title("Price by quantity and product")
plt.show()

Seaborn box plots of Price for Quantity levels 5 to 8, split by Product A and Product B; the Product B boxes sit higher than the Product A boxes at every quantity.

Use hue when the extra grouping answers a comparison question. If there are many groups, the boxes can become crowded; simplify the categories or create separate plots so the distributions remain readable.

Show Individual Observations with a Strip Plot

A box plot compresses the data into summary statistics. Overlay seaborn.stripplot() when readers also need to see each observation. Reuse the same axis and mappings for both layers, and add a small amount of horizontal jitter so points with similar values do not completely overlap.

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

data = pd.DataFrame({"Score": [12, 14, 15, 16, 18, 19, 21, 24]})
ax = sns.boxplot(data=data, y="Score", color="lightgray")
sns.stripplot(data=data, y="Score", color="darkred", jitter=0.15, ax=ax)
ax.set_title("Scores with individual observations")
plt.show()

The Score box plot drawn in light gray with a dark red strip plot overlaid, so each of the eight observations appears as its own jittered point inside or beside the box.

The box remains the summary, while each marker represents one row in the data. On a larger dataset, points may overlap; adjust the marker size or transparency to keep dense regions legible.

Customize the Box Plot

Use Matplotlib axis-limit methods to focus on a relevant value range, linewidth to change box and whisker border thickness, and palette to assign colors to categories. The orient parameter controls direction; for example, orient="h" requests a horizontal plot when the mappings are compatible. Axis limits only change the displayed view, so choose them carefully and keep the scale clear to avoid hiding relevant observations.

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

data = pd.DataFrame(
    {
        "Quantity": [5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8],
        "Price": [9, 10, 15, 16, 13, 14, 15, 18, 11, 12, 14, 15, 16, 17, 18, 19],
        "Product": ["A", "A", "A", "A", "B", "B", "B", "B"] * 2,
    }
)
ax = sns.boxplot(
    data=data,
    x="Quantity",
    y="Price",
    hue="Product",
    linewidth=2.5,
    palette="Set2",
)
ax.set_ylim(8, 20)
ax.set_title("Customized price comparison")
plt.show()

The customized price comparison with thicker box and whisker borders, Set2 palette colors for Product A and Product B, and the y-axis limited to the range from 8 to 20.

The palette colors correspond to the hue groups. To use horizontal boxes, swap the category and measurement mappings and set orient="h"; axis limits should then be applied with set_xlim() rather than set_ylim().

Create a Box Plot with catplot()

Use catplot() with kind="box" for the figure-level interface. It accepts the same explicit data mappings and can create separate panels with col or row when another variable should split the comparison. For a single plot, seaborn.boxplot() is the axes-level option; catplot() is convenient when the figure needs facets.

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

data = pd.DataFrame(
    {
        "Quantity": [5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8],
        "Price": [9, 10, 15, 16, 13, 14, 15, 18, 11, 12, 14, 15, 16, 17, 18, 19],
    }
)
grid = sns.catplot(data=data, x="Quantity", y="Price", kind="box")
grid.set_axis_labels("Quantity", "Price")
grid.fig.suptitle("Price by quantity")
plt.show()

Figure-level catplot with kind set to box: one box of Price for each Quantity level from 5 to 8 in a single panel, with the figure title above it.

Because catplot() builds the whole figure, it returns a grid object rather than a single axes; set labels and titles on the grid, as in the example, instead of calling ax.set_title().

Handle a Missing Column Error and Set the Whisker Range

Because every mapping names a DataFrame column, a misspelled name fails before anything is drawn. This call asks for a column Scroe that the data does not contain:

import pandas as pd
import seaborn as sns

data = pd.DataFrame({"Score": [12, 14, 15, 16, 18, 19, 21, 24]})
ax = sns.boxplot(data=data, y="Scroe")

The call ends with this error:

ValueError: Could not interpret value `Scroe` for `y`. An entry with this name does not appear in `data`.

Fix the name to match the column exactly — here, y="Score" — and the same call draws the plot. When the correct spelling is not obvious, print data.columns and copy the name from there; the check applies to x, y, and hue alike.

The whiskers are the other boundary readers meet. Add one far value, 60, to the scores: the quartiles barely move (first quartile 15, median 18, third quartile 21), and with the default whis=1.5 the upper whisker stops at 24 while 60 is drawn as a lone point beyond it. Passing whis as a pair of percentiles stretches the whiskers to those percentiles of the data instead:

import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns

scores = pd.DataFrame({"Score": [12, 14, 15, 16, 18, 19, 21, 24, 60]})
ax = sns.boxplot(data=scores, y="Score", whis=(0, 100))
plt.show()

With whis=(0, 100), the whiskers reach the minimum 12 and the maximum 60, and no points remain outside them. Prefer the default unless the full range is the message: stretched whiskers hide how unusual the extreme value is. Note also that an empty column does not raise an error — Seaborn returns an empty set of axes — so check the row count before plotting when the data comes from a pipeline.