Many guides still draw a Matplotlib multiple bar chart by hand, shifting each series with x - width/2 offsets. It works, but you’ll redo the math for every new series.
Matplotlib 3.11 has a cleaner answer: ax.grouped_bar(data, tick_labels=...) places the side-by-side bars for you.
In this tutorial, I’ll build a matplotlib multiple bar chart four ways on one sales dataset, plus separate charts in subplots.
The screenshots are real plot windows from Python 3.12.5, Matplotlib 3.11.2 and pandas 3.0.6.
The sample data: quarterly sales for a grouped bar chart
Every chart uses the same numbers: 2026 sales in thousand USD for four regions. Quarters are the groups; regions are the bars inside each group.
Watch two things: West dips in Q2, and South leads from Q2 on. New to bars? Start with plotting a basic bar chart in Matplotlib.
Method 1: Grouped bar chart with grouped_bar() (Recommended)
Use this if you’re on Matplotlib 3.11 or newer:
import matplotlib.pyplot as plt
quarters = ["Q1 2026", "Q2 2026", "Q3 2026", "Q4 2026"]
sales = {
"Northeast": [182.4, 195.1, 201.7, 236.9],
"Midwest": [154.2, 161.8, 170.3, 188.5],
"South": [201.5, 214.0, 229.6, 251.3],
"West": [210.3, 198.6, 225.2, 248.4],
}
fig, ax = plt.subplots(figsize=(9, 4.8))
ax.grouped_bar(sales, tick_labels=quarters, group_spacing=1.2)
ax.set_ylabel("Sales (thousand USD)")
ax.legend(title="Region", ncols=4, loc="upper left")
ax.set_ylim(0, 300)
plt.tight_layout()
plt.show()

grouped_bar() call draws four bars per quarter and labels them for the legend.How does this code work?
The dict keys become the legend labels. tick_labels names the groups, and group_spacing sets the gap between quarters.
Important: the API is still provisional, so pin your Matplotlib version.
Method 2: Double bar graph with ax.bar() offsets and data labels
This classic approach works on any version. You’ll use the same data, cut to Northeast and West:
import matplotlib.pyplot as plt
import numpy as np
quarters = ["Q1", "Q2", "Q3", "Q4"]
northeast = [182.4, 195.1, 201.7, 236.9]
west = [210.3, 198.6, 225.2, 248.4]
x = np.arange(len(quarters))
width = 0.38
fig, ax = plt.subplots(figsize=(8, 4.5))
ne = ax.bar(x - width / 2, northeast, width, label="Northeast")
we = ax.bar(x + width / 2, west, width, label="West")
ax.bar_label(ne, fmt="${:,.0f}K", padding=2, fontsize=9)
ax.bar_label(we, fmt="${:,.0f}K", padding=2, fontsize=9)
ax.set_xticks(x, quarters)
ax.set_ylabel("Sales (thousand USD)")
ax.legend(loc="upper left")
ax.set_ylim(0, 290)
plt.tight_layout()
plt.show()

How does this code work?
np.arange(4) gives one slot per quarter. Northeast moves half a bar left and West half a bar right, so they don’t overlap.
bar_label() takes the container that ax.bar() returns, and fmt turns 182.4 into $182K. More styles are in showing values on Matplotlib bars.
For n series in a loop, use width = 0.8 / n and offset = (i - (n - 1) / 2) * width. Crowded labels are handled in labeling a multiple bar chart.
Method 3: Pandas bar plot of multiple columns with custom colors
If your numbers sit in a DataFrame, pandas groups them: each column’s a series, each row a group. Here are three of the regions:
import matplotlib.pyplot as plt
import pandas as pd
df = pd.DataFrame(
{"Northeast": [182.4, 195.1, 201.7, 236.9],
"South": [201.5, 214.0, 229.6, 251.3],
"West": [210.3, 198.6, 225.2, 248.4]},
index=["Q1", "Q2", "Q3", "Q4"])
colors = ["#1b4f72", "#f39c12", "#c0392b"]
ax = df.plot.bar(figsize=(9, 4.8), rot=0, width=0.8,
color=colors, edgecolor="white")
ax.set_ylabel("Sales (thousand USD)")
plt.tight_layout()
plt.show()
print(df.idxmax(axis=1).to_dict())
Output:
{'Q1': 'West', 'Q2': 'South', 'Q3': 'South', 'Q4': 'South'}

df.plot.bar() groups the region columns under each quarter in your colors.The printed dict matches the chart: West leads Q1, South leads Q2 to Q4.
How does this code work?
width=0.8 is the space a group’s bars share, and color takes one color per column. See building a Matplotlib bar chart from a DataFrame.
Pro Tip: ax.grouped_bar(df) accepts the DataFrame too.
Method 4: Horizontal grouped bar chart
Same regions, now comparing Q1 with Q4:
import matplotlib.pyplot as plt
regions = ["Northeast", "Midwest", "South", "West"]
data = {"Q1 2026": [182.4, 154.2, 201.5, 210.3],
"Q4 2026": [236.9, 188.5, 251.3, 248.4]}
fig, ax = plt.subplots(figsize=(8, 4.5))
res = ax.grouped_bar(data, tick_labels=regions,
orientation="horizontal")
ax.bar_label(res.bar_containers[1], fmt="${:,.0f}K", padding=3)
ax.invert_yaxis()
ax.set_xlabel("Sales (thousand USD)")
ax.set_xlim(0, 290)
ax.legend()
plt.tight_layout()
plt.show()

How does this code work?
orientation="horizontal" switches to barh. res.bar_containers[1] is the Q4 series, so only it gets labels. Every region grew; South ends highest at $251K.
invert_yaxis() keeps Northeast on top. Single-series versions are in the Matplotlib horizontal bar chart guide.
Separate bar charts side by side with plt.subplots()
Need separate charts instead? Use one subplot per region:
import matplotlib.pyplot as plt
quarters = ["Q1", "Q2", "Q3", "Q4"]
sales = {"South": [201.5, 214.0, 229.6, 251.3],
"West": [210.3, 198.6, 225.2, 248.4]}
fig, axes = plt.subplots(1, 2, figsize=(9, 3.6), sharey=True)
for ax, (region, values) in zip(axes, sales.items()):
ax.bar(quarters, values, color="#2e86c1")
ax.set_title(region)
axes[0].set_ylabel("Sales (thousand USD)")
plt.tight_layout()
plt.show()

sharey=True keeps both scales equal. To fix it yourself, try setting the bar chart y-axis range.
Which multiple bar chart method should you use?
| Method | Use it when |
|---|---|
grouped_bar() | Matplotlib 3.11+ |
ax.bar() offsets | Older versions, full control |
df.plot.bar() | Data already in a DataFrame |
plt.subplots() | Very different ranges |
I’d pick grouped_bar() for new code. For totals, use a stacked bar chart in Matplotlib; for target vs actual, try overlaying two bar charts.
Things to Keep in Mind with grouped bar charts
- Keep a group’s bars under 0.8 total width.
- Use one quarter order in every series, or bars get mislabeled.
- For date groups, see a time series multiple bar plot.
More Matplotlib charts you’ll like:
- Add error bars to a Matplotlib chart
- Matplotlib bar chart labels
- Make a pie chart in Matplotlib
- Save a Matplotlib figure as PDF
Frequently asked questions
How do I plot multiple bars side by side in Matplotlib?
Use ax.grouped_bar() on 3.11+, or offset each ax.bar() series.
How do I make a double bar graph in Python?
Two ax.bar() calls at x - width/2 and x + width/2.
How do I add data labels to a grouped bar chart?
Call ax.bar_label() on each bar container.
How do I plot multiple columns as a bar chart in pandas?
df.plot.bar() draws one bar per column for each row.
How do I calculate bar offsets for n series?
width = 0.8 / n, offset = (i - (n - 1) / 2) * width.