Bar Charts
A bar chart compares magnitudes across categories, and it is the right tool when the categories are discrete - products, months, regions, answers to a survey. Two rules govern good bar charts: the axis should start at zero (a truncated axis turns small differences into dramatic ones), and bars should be sorted or ordered by a meaningful logic (value, time, or hierarchy). Matplotlib's bar and barh cover vertical and horizontal forms with full control over width, position, and stacking.
Basic Bar Chart
import matplotlib.pyplot as plt
categories = ["Search", "Direct", "Referral", "Social", "Email"]
values = [4200, 3100, 1800, 950, 640]
fig, ax = plt.subplots()
bars = ax.bar(categories, values, color="tab:blue", width=0.6)
ax.set_ylabel("visits")
ax.set_ylim(0, max(values) * 1.15) # room for value labels
for bar, v in zip(bars, values):
ax.text(bar.get_x() + bar.get_width() / 2, v + 80,
f"{v:,}", ha="center", va="bottom", fontsize=9)
plt.show()
bar returns the Rectangle artists, which makes value labels a two-line loop: read each bar's position from the artist and place text above it. set_ylim starting at zero is the honest default for magnitude comparison.
Horizontal Bars
import matplotlib.pyplot as plt
regions = ["APAC", "EMEA", "Americas", "LATAM", "ANZ"]
revenue = [2.1, 3.4, 5.8, 0.9, 0.4] # in $M
fig, ax = plt.subplots()
ax.barh(regions, revenue, color="tab:green")
ax.set_xlabel("revenue ($M)")
ax.invert_yaxis() # largest on top, matching the label order
plt.show()
Horizontal bars are the better form when category labels are long or numerous - the label column never overlaps the bars. invert_yaxis puts the first (usually largest) category on top, matching how people read rankings.
Grouped (Side-by-Side) Bars
import numpy as np
import matplotlib.pyplot as plt
months = ["Jan", "Feb", "Mar", "Apr"]
this_year = [120, 150, 135, 170]
last_year = [100, 120, 140, 130]
x = np.arange(len(months))
width = 0.38
fig, ax = plt.subplots()
ax.bar(x - width / 2, last_year, width, label="2025", color="tab:gray")
ax.bar(x + width / 2, this_year, width, label="2026", color="tab:blue")
ax.set_xticks(x, months)
ax.legend()
ax.set_ylabel("sales")
plt.show()
Grouped bars need numeric x positions so two bars per category can sit side by side; set_xticks then relabels the numeric positions with the category names. Keep groups to two or three series - beyond that, a line chart or small multiples communicate better.
Stacked Bars
import matplotlib.pyplot as plt
quarters = ["Q1", "Q2", "Q3", "Q4"]
hardware = [40, 55, 60, 75]
services = [25, 30, 45, 50]
fig, ax = plt.subplots()
ax.bar(quarters, hardware, label="hardware", color="tab:blue")
ax.bar(quarters, services, bottom=hardware, label="services", color="tab:orange")
ax.legend()
ax.set_ylabel("revenue")
plt.show()
The bottom argument stacks segments: each series sits on the cumulative sum of the ones before it. Stacked bars show total-plus-composition, but comparing individual segments across categories becomes hard - reserve them for "the whole matters" charts.
Summary
Build bar charts with bar/barh, always zero-based for magnitude comparison, sorted by a real logic, with value labels from the returned artists. Horizontal form for long labels, numeric x positions for grouped bars, and bottom for composition - and stop at three series before the chart stops being readable.