How to Create Multiple Bar Charts in Matplotlib (Grouped, Side by Side)

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()
Matplotlib grouped bar chart from grouped_bar() with 2026 quarterly sales for four US regions side by side
One 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()
Matplotlib double bar graph of Northeast and West 2026 sales side by side with dollar data labels from bar_label
Each pair sits on one quarter tick, with values on top.

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'}
pandas grouped bar chart of three region columns per quarter with custom colors from DataFrame.plot.bar
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()
Matplotlib horizontal grouped bar chart comparing Q1 and Q4 2026 sales per region with grouped_bar orientation
Q1 and Q4 bars for each region, with dollar labels on Q4.

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()
Two Matplotlib bar charts side by side in subplots showing 2026 quarterly sales for the South and West regions
Two separate charts on one shared sales axis.

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?

MethodUse it when
grouped_bar()Matplotlib 3.11+
ax.bar() offsetsOlder 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:

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.