Seaborn Barplot: The Complete Guide to Bar Charts in Python
Updated on
You need to compare averages across categories. Maybe it is average revenue by region, mean test scores by classroom, or median response time by server. A raw table of numbers forces you to scan rows and do mental math. A well-constructed bar chart makes the answer obvious at a glance -- and Python's seaborn library lets you build one in a single function call.
The problem is that sns.barplot() has a surprisingly deep set of parameters. Grouping with hue, controlling error bars, reordering categories, choosing palettes, going horizontal -- each of these trips up newcomers and even experienced users who have not touched the function in a while. Poorly configured bar charts mislead readers or simply look bad.
This guide covers every practical use case for sns.barplot(). Every code block is copy-paste ready, uses real or realistic datasets, and produces clean output. By the end, you will know how to build publication-quality bar charts, understand the difference between barplot and countplot, and have a quick path to interactive exploration with PyGWalker.
What sns.barplot() Does
sns.barplot() draws a bar chart where the height (or length) of each bar represents the central tendency of a numeric variable for each category. By default, it computes the mean and draws a 95% confidence interval as an error bar on top of each bar.
This makes it different from a simple count-based bar chart. It is a statistical visualization: it aggregates your data before plotting.
Basic Syntax
import seaborn as sns
import matplotlib.pyplot as plt
sns.barplot(data=df, x="category_column", y="numeric_column")
plt.show()Key parameters at a glance:
| Parameter | Purpose | Default |
|---|---|---|
data | DataFrame containing the data | Required |
x / y | Column names for category and value axes | Required |
hue | Column for grouping bars by color | None |
estimator | Aggregation function (mean, median, sum, etc.) | mean |
errorbar | Type of error bar ("ci", "sd", "se", "pi", or None) | ("ci", 95) |
order | Explicit category order | None (data order) |
palette | Color scheme | None (seaborn default) |
orient | Bar orientation ("v" or "h") | Auto-detected |
width | Width of each bar | 0.8 |
saturation | Color saturation level | 0.75 |
ax | Matplotlib Axes to draw on | Current Axes |
Simple Vertical Bar Chart
Start with the built-in tips dataset. This DataFrame records restaurant bills, tips, and customer attributes.
import seaborn as sns
import matplotlib.pyplot as plt
tips = sns.load_dataset("tips")
plt.figure(figsize=(8, 5))
sns.barplot(data=tips, x="day", y="total_bill")
plt.title("Average Total Bill by Day")
plt.ylabel("Average Total Bill ($)")
plt.xlabel("Day of Week")
plt.tight_layout()
plt.show()Each bar shows the mean total_bill for that day. The black lines on top are 95% confidence intervals, giving you a sense of how much the mean might vary. Thursday has the lowest average bill, while Sunday tends to be higher.
To switch the aggregation function, pass estimator:
import numpy as np
sns.barplot(data=tips, x="day", y="total_bill", estimator=np.median)
plt.title("Median Total Bill by Day")
plt.show()Horizontal Bar Chart
Horizontal bars work better when category labels are long or when you have many categories. There are two ways to make a horizontal barplot.
Option 1: Swap x and y.
plt.figure(figsize=(8, 5))
sns.barplot(data=tips, x="total_bill", y="day")
plt.title("Average Total Bill by Day (Horizontal)")
plt.xlabel("Average Total Bill ($)")
plt.ylabel("")
plt.tight_layout()
plt.show()When the numeric column is on x and the categorical column is on y, seaborn automatically draws horizontal bars.
Option 2: Use the orient parameter.
sns.barplot(data=tips, x="total_bill", y="day", orient="h")
plt.show()Both approaches produce the same result. Swapping x and y is the more common pattern.
Grouped (Hue) Bar Chart
The hue parameter splits each category bar into sub-bars based on a second categorical variable. This is essential for comparing subgroups side by side.
plt.figure(figsize=(9, 5))
sns.barplot(data=tips, x="day", y="total_bill", hue="sex")
plt.title("Average Total Bill by Day and Gender")
plt.ylabel("Average Total Bill ($)")
plt.legend(title="Gender")
plt.tight_layout()
plt.show()Each day now shows two bars -- one for Male, one for Female. The legend maps colors to groups. You can immediately see that male customers tend to have slightly higher average bills on most days.
For three or more hue levels, the bars become narrower. If readability suffers, consider a faceted approach with sns.catplot() instead:
g = sns.catplot(data=tips, x="day", y="total_bill", hue="sex",
col="time", kind="bar", height=4, aspect=1.2)
g.set_axis_labels("Day", "Average Total Bill ($)")
g.set_titles("{col_name}")
plt.tight_layout()
plt.show()This creates separate panels for Lunch and Dinner, each with grouped bars by gender -- much easier to read when you have multiple grouping dimensions.
Customizing Colors with Palette
The palette parameter controls the color scheme. Seaborn ships with many built-in palettes.
plt.figure(figsize=(8, 5))
sns.barplot(data=tips, x="day", y="total_bill", hue="sex",
palette="Set2")
plt.title("Custom Palette: Set2")
plt.tight_layout()
plt.show()Popular palette choices:
| Palette | Style | Best For |
|---|---|---|
"Set2" | Muted, distinct | Categorical comparisons |
"pastel" | Soft tones | Presentations, light backgrounds |
"deep" | Rich, saturated | Default seaborn look |
"viridis" | Perceptually uniform | Accessibility-friendly |
"coolwarm" | Diverging blue-red | Contrasting two groups |
"husl" | Evenly spaced hues | Many categories |
You can also pass a list of specific hex colors:
sns.barplot(data=tips, x="day", y="total_bill",
palette=["#2ecc71", "#e74c3c", "#3498db", "#f39c12"])
plt.show()Or use a dictionary to map specific categories to colors:
day_colors = {"Thur": "#636e72", "Fri": "#e17055",
"Sat": "#0984e3", "Sun": "#6c5ce7"}
sns.barplot(data=tips, x="day", y="total_bill", palette=day_colors)
plt.show()Error Bars and Confidence Intervals
By default, sns.barplot() shows a 95% confidence interval. This is the bootstrapped CI around the mean. You have full control over what gets displayed.
import matplotlib.pyplot as plt
import seaborn as sns
tips = sns.load_dataset("tips")
fig, axes = plt.subplots(1, 4, figsize=(18, 4), sharey=True)
# 95% CI (default)
sns.barplot(data=tips, x="day", y="total_bill", errorbar=("ci", 95), ax=axes[0])
axes[0].set_title("95% CI (default)")
# Standard deviation
sns.barplot(data=tips, x="day", y="total_bill", errorbar="sd", ax=axes[1])
axes[1].set_title("Standard Deviation")
# Standard error
sns.barplot(data=tips, x="day", y="total_bill", errorbar="se", ax=axes[2])
axes[2].set_title("Standard Error")
# No error bars
sns.barplot(data=tips, x="day", y="total_bill", errorbar=None, ax=axes[3])
axes[3].set_title("No Error Bars")
plt.tight_layout()
plt.show()| errorbar Value | What It Shows | When to Use |
|---|---|---|
("ci", 95) | 95% bootstrapped confidence interval | Default; good for statistical inference |
"sd" | Standard deviation | Show data spread, not estimation uncertainty |
"se" | Standard error of the mean | Common in scientific papers |
("pi", 95) | 95% percentile interval | Show range covering 95% of observations |
None | No error bars | Clean visuals when uncertainty is not the focus |
Note: In older versions of seaborn (before 0.12), the parameter was called
ciinstead oferrorbar. If you seeci=95in legacy code, it does the same thing. Modern seaborn useserrorbarfor more flexibility.
Ordering Bars
By default, bars appear in the order the categories show up in your data. Use the order parameter to control this explicitly.
plt.figure(figsize=(8, 5))
sns.barplot(data=tips, x="day", y="total_bill",
order=["Thur", "Fri", "Sat", "Sun"])
plt.title("Bars in Chronological Order")
plt.tight_layout()
plt.show()To order bars by their value (largest to smallest), compute the means first:
day_order = tips.groupby("day")["total_bill"].mean().sort_values(ascending=False).index
plt.figure(figsize=(8, 5))
sns.barplot(data=tips, x="day", y="total_bill", order=day_order)
plt.title("Days Ordered by Average Bill (Descending)")
plt.tight_layout()
plt.show()This pattern -- sorting by the aggregated metric -- is one of the most common ways to make bar charts immediately informative.
sns.barplot() vs sns.countplot() -- When to Use Which
These two functions look similar but serve different purposes. Choosing the wrong one is a common source of confusion.
| Feature | sns.barplot() | sns.countplot() |
|---|---|---|
| What it plots | Aggregated metric (mean, median, sum) of a numeric variable per category | Count of observations per category |
| Requires numeric y? | Yes | No (only a categorical variable) |
| Default statistic | Mean | Count |
| Error bars | Yes (confidence interval by default) | No |
| Use case | "What is the average tip by day?" | "How many meals were served each day?" |
| Syntax | sns.barplot(x="day", y="tip", data=tips) | sns.countplot(x="day", data=tips) |
| Equivalent to | A bar chart of df.groupby("day")["tip"].mean() | A bar chart of df["day"].value_counts() |
Rule of thumb: If you have a numeric column you want to aggregate, use barplot. If you just want to count how many rows fall into each category, use countplot.
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# barplot: average tip per day
sns.barplot(data=tips, x="day", y="tip", ax=axes[0])
axes[0].set_title("sns.barplot() -- Average Tip by Day")
axes[0].set_ylabel("Average Tip ($)")
# countplot: number of records per day
sns.countplot(data=tips, x="day", ax=axes[1])
axes[1].set_title("sns.countplot() -- Meals per Day")
axes[1].set_ylabel("Count")
plt.tight_layout()
plt.show()Customizing with Matplotlib
Since seaborn builds on matplotlib, you have full access to matplotlib's customization layer. This is how you add titles, rotate labels, adjust fonts, and annotate bars.
Titles, Labels, and Rotation
plt.figure(figsize=(8, 5))
ax = sns.barplot(data=tips, x="day", y="total_bill", palette="muted")
ax.set_title("Average Total Bill by Day", fontsize=16, fontweight="bold")
ax.set_xlabel("Day of the Week", fontsize=12)
ax.set_ylabel("Average Bill ($)", fontsize=12)
ax.tick_params(axis='x', rotation=45)
plt.tight_layout()
plt.show()Adding Value Labels on Bars
plt.figure(figsize=(8, 5))
ax = sns.barplot(data=tips, x="day", y="total_bill", errorbar=None,
palette="Blues_d",
order=["Thur", "Fri", "Sat", "Sun"])
# Annotate each bar with its value
for container in ax.containers:
ax.bar_label(container, fmt="%.1f", fontsize=11, padding=3)
ax.set_title("Average Total Bill by Day")
ax.set_ylabel("Average Bill ($)")
ax.set_ylim(0, 25)
plt.tight_layout()
plt.show()The ax.bar_label() method (added in matplotlib 3.4) is the cleanest way to add value annotations. The padding argument controls distance from the bar top.
Stacked Bars (Workaround)
Seaborn does not natively support stacked bar charts. This is a deliberate design choice -- stacked bars make it harder to compare individual segment sizes. However, if your use case calls for them, you can achieve the effect with pandas plotting or a manual matplotlib approach.
Using Pandas .plot(kind="bar", stacked=True)
import pandas as pd
import matplotlib.pyplot as plt
tips = pd.read_csv("https://raw.githubusercontent.com/mwaskom/seaborn-data/master/tips.csv")
# Pivot to get counts per day and time
pivot = tips.groupby(["day", "time"]).size().unstack(fill_value=0)
# Reorder days
pivot = pivot.loc[["Thur", "Fri", "Sat", "Sun"]]
pivot.plot(kind="bar", stacked=True, figsize=(8, 5),
color=["#3498db", "#e74c3c"], edgecolor="white")
plt.title("Meal Count by Day (Stacked by Lunch/Dinner)")
plt.ylabel("Number of Meals")
plt.xlabel("Day")
plt.xticks(rotation=0)
plt.legend(title="Time")
plt.tight_layout()
plt.show()Manual Stacking with Matplotlib
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
tips = sns.load_dataset("tips")
pivot = tips.groupby(["day", "time"])["total_bill"].mean().unstack(fill_value=0)
pivot = pivot.loc[["Thur", "Fri", "Sat", "Sun"]]
days = pivot.index
lunch = pivot["Lunch"].values
dinner = pivot["Dinner"].values
x = np.arange(len(days))
width = 0.6
fig, ax = plt.subplots(figsize=(8, 5))
ax.bar(x, lunch, width, label="Lunch", color="#3498db")
ax.bar(x, dinner, width, bottom=lunch, label="Dinner", color="#e74c3c")
ax.set_xticks(x)
ax.set_xticklabels(days)
ax.set_ylabel("Average Total Bill ($)")
ax.set_title("Average Bill by Day (Stacked: Lunch + Dinner)")
ax.legend()
plt.tight_layout()
plt.show()Both approaches work. The pandas method is more concise; the matplotlib method gives you more control over positioning and styling.
Create Interactive Bar Charts with PyGWalker
Static bar charts are great for reports and papers. But during exploratory analysis, you often want to switch axes, try different aggregations, filter data, and compare chart types -- all without rewriting code each time.
PyGWalker (opens in a new tab) (Python binding of Graphic Walker) turns any pandas DataFrame into an interactive, Tableau-like visualization interface directly inside Jupyter Notebook. You can build bar charts, grouped bar charts, and dozens of other chart types by dragging and dropping fields.
pip install pygwalkerimport pandas as pd
import pygwalker as pyg
tips = pd.read_csv("https://raw.githubusercontent.com/mwaskom/seaborn-data/master/tips.csv")
walker = pyg.walk(tips)Once the interface loads, you can:
- Drag
dayto the X axis andtotal_billto the Y axis to create a bar chart instantly. - Drop
sexortimeinto the Color channel to get a grouped bar chart. - Switch the aggregation from mean to sum, median, or count with a single click.
- Apply filters (e.g., only Saturday and Sunday) without writing any pandas code.
- Export your chart configuration for reproducibility.
This is particularly useful during the exploration phase -- quickly test which groupings and aggregations reveal the most insight, then lock in your final static visualization with sns.barplot() for sharing.
Frequently Asked Questions
How do I change the color of individual bars in sns.barplot()?
Pass a list of colors to the palette parameter. The list length should match the number of categories. For example: sns.barplot(data=tips, x="day", y="total_bill", palette=["#e74c3c", "#3498db", "#2ecc71", "#f39c12"]). You can also pass a dictionary mapping category names to colors for explicit control.
What is the difference between sns.barplot() and plt.bar()?
sns.barplot() works directly with DataFrames, automatically computes an aggregate statistic (default: mean), and draws confidence intervals. plt.bar() from matplotlib requires pre-computed bar heights and positions -- it draws exactly what you give it with no aggregation. Use sns.barplot() for quick statistical summaries and plt.bar() for full manual control.
How do I remove error bars from a seaborn barplot?
Set errorbar=None in the function call: sns.barplot(data=tips, x="day", y="total_bill", errorbar=None). In seaborn versions before 0.12, use ci=None instead.
Can sns.barplot() show the sum instead of the mean?
Yes. Pass estimator=sum to the function: sns.barplot(data=tips, x="day", y="total_bill", estimator=sum). You can use any function that takes an array and returns a scalar, including numpy.median, numpy.std, or a custom lambda.
How do I make a horizontal barplot in seaborn?
Place the numeric column on the x axis and the categorical column on y: sns.barplot(data=tips, x="total_bill", y="day"). Seaborn detects the orientation automatically. You can also explicitly set orient="h".
Conclusion
The seaborn barplot is the fastest way to visualize aggregated metrics across categories in Python. From a single line that shows mean values with confidence intervals, to grouped comparisons with hue, to fully styled charts with value annotations -- sns.barplot() covers the full range of bar chart needs.
Start simple: pass your DataFrame, pick your x and y columns, and let seaborn handle the aggregation and styling. Then layer on customizations as needed -- reorder bars by value, switch to a horizontal layout for long labels, or turn off error bars for cleaner presentation slides.
When you need stacked bars, reach for pandas or matplotlib directly. When you want to explore your data interactively before committing to a specific chart configuration, PyGWalker (opens in a new tab) gives you a drag-and-drop interface that works inside Jupyter without extra code.
All the code examples in this guide are ready to copy and run. Pick the pattern closest to your use case, swap in your data, and you will have a clear, informative bar chart in under a minute.