To make a grouped bar chart in Matplotlib, call ax.bar() once for each dataset and offset each dataset’s x positions so its bars sit beside the others for the same category. Put the x-axis ticks at the unshifted category centers and add a legend to identify each series. This approach works across Matplotlib versions; Matplotlib 3.11 also adds a simpler, explicitly provisional ax.grouped_bar() API.
Build a grouped bar chart with offset positions
Each group represents one category, and each bar within that group represents a dataset. The following example compares two series across three categories. It follows the offset pattern in Matplotlib’s grouped bar chart gallery.
import matplotlib.pyplot as plt
import numpy as np
categories = ["G1", "G2", "G3"]
series_a = [20, 34, 30]
series_b = [25, 32, 34]
x = np.arange(len(categories))
width = 0.35
fig, ax = plt.subplots(layout="constrained")
bar_a = ax.bar(x - width / 2, series_a, width, label="Series A")
bar_b = ax.bar(x + width / 2, series_b, width, label="Series B")
ax.set_xticks(x, categories)
ax.set_ylabel("Value")
ax.legend()
ax.bar_label(bar_a, padding=3)
ax.bar_label(bar_b, padding=3)
plt.show()
x contains the center position of each category. Shifting one series left by half the bar width and the other right by half keeps the pair centered over that category. Setting ticks at x, rather than at either shifted position, centers each category label under its group.
Adjust the offsets for more datasets
For more than two datasets, distribute the bars evenly across a chosen group width, centered on each category. If there are n datasets, use a bar width of group_width / n and offsets of (i - (n - 1) / 2) * width for dataset index i, from 0 to n - 1. Apply the same category-center array to every series and retain the unshifted centers for the ticks.
#1 Best Overall
Use grouped_bar in Matplotlib 3.11 and later
The stable API reference for Matplotlib 3.11.2 documents Axes.grouped_bar, added in version 3.11. The method is designed for datasets that share categories, but Matplotlib describes the API as provisional, so its interface may change. If you need compatibility with an earlier installation, use explicit ax.bar() calls instead. See the grouped_bar API reference.
The method accepts same-length array-like datasets in a list, a dictionary of dataset names and values, a 2D array, or a pandas DataFrame. For a DataFrame, the index supplies categories and the columns supply datasets. For a dictionary, its keys provide series labels; do not also pass labels.
Rank #2
fig, ax = plt.subplots(layout="constrained")
result = ax.grouped_bar(data, tick_labels=categories, group_spacing=1)
for container in result.bar_containers:
ax.bar_label(container, padding=3)
ax.legend()
Here, data is one of the supported input forms. The documented return object guarantees bar_containers and remove(); other return-object behavior should not be assumed. Its group_spacing setting controls the gap between category groups, while bar_spacing controls the gap between bars within a group. The documented defaults are 1.5 bar widths between groups and no gap between bars within a group. Other controls include positions, tick_labels, labels, orientation, and colors.
Choose between manual offsets and grouped_bar
| Approach | Matplotlib availability | Position and style control | Best fit |
|---|---|---|---|
Repeated ax.bar() calls with offsets |
Available without relying on the 3.11 method | Direct control over each call’s positions and styling | Compatibility and custom placement |
ax.grouped_bar() |
Added in Matplotlib 3.11; provisional | Provides grouped categorical options such as spacing, orientation, and colors | Convenience when datasets share categories and the installed version supports it |
Check alignment, labels, and readability
- Make sure every dataset has the same number of values and that each position refers to the same category in every dataset. The
grouped_barlist and dictionary inputs require equal-length sequences. - Give each series a distinct legend label so readers can map its visual encoding to the dataset.
- Use
ax.bar_label()on theBarContainerreturned by eachax.bar()call, or on each container inresult.bar_containersforgrouped_bar. Omit value labels if they collide or become difficult to read.
When horizontal bars are a better fit
Long category names can be easier to read on a horizontal axis. Matplotlib’s barh method uses categorical y positions and supports the same bar-label workflow; see the barh API reference.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Repair Windows errors before they cause bigger problemsFix Now →Quick Recap
Best Value
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




