To create a stacked bar chart in Matplotlib, draw the first series with plt.bar(), then draw each next series with the bottom argument set to the height of everything below it. For a horizontal stacked bar chart, use plt.barh() with left instead. This guide builds stacked bar charts in Python step by step: two series, many series in a for loop, value labels and totals, 100% stacked bars, horizontal bars, pandas DataFrames and negative values.
Tested with Python 3.12.5, Matplotlib 3.11.2, NumPy 2.5.3 and pandas 3.0.6; every chart is a screenshot of the real Matplotlib window. See the official Matplotlib stacked bar chart example and the pyplot.bar reference for all options.
Basic stacked bar chart with bottom=
import matplotlib.pyplot as plt
quarters = ["Q1", "Q2", "Q3", "Q4"]
online = [120, 150, 170, 210]
retail = [200, 190, 185, 220]
plt.bar(quarters, online, label="Online")
plt.bar(quarters, retail, bottom=online, label="Retail") # bottom= starts each bar on top of the first one
plt.ylabel("Sales (thousand USD)")
plt.title("Sales by channel")
plt.legend()
plt.show()
bottom=online.Stacked bars are best when the total matters as much as the parts. If you want to compare the series with each other rather than the total, use side-by-side (grouped) bars instead.
Stacked bar chart with a for loop
With three or more series, keep a running bottom array and add each series to it inside a loop. A NumPy array makes the addition element-wise:
import matplotlib.pyplot as plt
import numpy as np
quarters = ["Q1", "Q2", "Q3", "Q4"]
sales = {
"Online": [120, 150, 170, 210],
"Retail": [200, 190, 185, 220],
"Wholesale": [80, 95, 110, 105],
"Partners": [40, 55, 60, 75],
}
fig, ax = plt.subplots(figsize=(7, 4.5))
bottom = np.zeros(len(quarters))
for channel, values in sales.items():
ax.bar(quarters, values, bottom=bottom, label=channel)
bottom += values # the next series starts where this one ends
print(f"{channel:<10} bottom after stacking: {bottom}")
ax.set_ylabel("Sales (thousand USD)")
ax.legend(loc="upper left")
plt.tight_layout()
plt.show()
Output:
Online bottom after stacking: [120. 150. 170. 210.]
Retail bottom after stacking: [320. 340. 355. 430.]
Wholesale bottom after stacking: [400. 435. 465. 535.]
Partners bottom after stacking: [440. 490. 525. 610.]
bottom after each series, printed in the Command Prompt.Using a plain Python list for bottom would not work: list += list joins the lists instead of adding the numbers.
Add value labels and totals to a stacked bar chart
ax.bar_label() (Matplotlib 3.4+) writes the values on a set of bars. Use label_type="center" for the segments, then label the top series once more with the totals:
import matplotlib.pyplot as plt
import numpy as np
quarters = ["Q1", "Q2", "Q3", "Q4"]
sales = {"Online": [120, 150, 170, 210], "Retail": [200, 190, 185, 220], "Wholesale": [80, 95, 110, 105]}
fig, ax = plt.subplots(figsize=(7, 4.5))
bottom = np.zeros(len(quarters))
for channel, values in sales.items():
bars = ax.bar(quarters, values, bottom=bottom, label=channel)
ax.bar_label(bars, label_type="center", color="white", fontsize=9) # value inside each segment
bottom += values
ax.bar_label(bars, labels=[f"{t:.0f}" for t in bottom], padding=3, fontweight="bold") # total on top
ax.set_ylim(0, bottom.max() * 1.1)
ax.set_ylabel("Sales (thousand USD)")
ax.legend(loc="upper left")
plt.tight_layout()
plt.show()
100% stacked bar chart (percentages)
To compare shares instead of amounts, divide every column by its total so each bar reaches 100:
import matplotlib.pyplot as plt
import numpy as np
quarters = ["Q1", "Q2", "Q3", "Q4"]
sales = np.array([[120, 150, 170, 210], # Online
[200, 190, 185, 220], # Retail
[80, 95, 110, 105]]) # Wholesale
channels = ["Online", "Retail", "Wholesale"]
share = sales / sales.sum(axis=0) * 100 # each column now adds up to 100
fig, ax = plt.subplots(figsize=(7, 4.5))
bottom = np.zeros(len(quarters))
for name, row in zip(channels, share):
bars = ax.bar(quarters, row, bottom=bottom, label=name)
ax.bar_label(bars, labels=[f"{v:.0f}%" for v in row], label_type="center", color="white")
bottom += row
ax.set_ylabel("Share of sales (%)")
ax.legend(loc="upper left", bbox_to_anchor=(1, 1))
plt.tight_layout()
plt.show()
Horizontal stacked bar chart
barh() draws horizontal bars. Stack them with left= instead of bottom=. This layout suits long category names and survey results:
import matplotlib.pyplot as plt
import numpy as np
teams = ["Support", "Sales", "Engineering", "Marketing"]
answers = {"Agree": [45, 38, 52, 30], "Neutral": [30, 35, 28, 40], "Disagree": [25, 27, 20, 30]}
colors = ["#2e7d32", "#bdbdbd", "#c62828"]
fig, ax = plt.subplots(figsize=(8, 3.8))
left = np.zeros(len(teams))
for (answer, values), color in zip(answers.items(), colors):
bars = ax.barh(teams, values, left=left, label=answer, color=color) # barh stacks with left=
ax.bar_label(bars, label_type="center", fmt="%d%%")
left += values
ax.invert_yaxis() # first team at the top
ax.set_xlabel("Responses (%)")
ax.legend(ncols=3, loc="lower center", bbox_to_anchor=(0.5, 1.0))
plt.tight_layout()
plt.show()
Stacked bar chart from a pandas DataFrame
With pandas, one line does the stacking: df.plot(kind="bar", stacked=True). Each row becomes a bar and each column a segment. Use kind="barh" for horizontal bars.
import pandas as pd
import matplotlib.pyplot as plt
df = pd.DataFrame({
"quarter": ["Q1", "Q2", "Q3", "Q4"],
"Online": [120, 150, 170, 210],
"Retail": [200, 190, 185, 220],
"Wholesale": [80, 95, 110, 105],
}).set_index("quarter")
ax = df.plot(kind="bar", stacked=True, figsize=(7, 4.5), rot=0) # one stacked bar per row
for container in ax.containers:
ax.bar_label(container, label_type="center", color="white", fontsize=9)
ax.set_ylabel("Sales (thousand USD)")
plt.tight_layout()
plt.show()
df.plot(kind="bar", stacked=True) with labels from ax.containers.Stacked bar chart with negative values
If you pass negative numbers with a single running bottom, segments overlap. Keep two running totals, one for positive values stacking up and one for negative values stacking down:
import matplotlib.pyplot as plt
import numpy as np
months = ["Jan", "Feb", "Mar", "Apr", "May"]
data = {"Product A": np.array([30, -10, 25, -15, 20]),
"Product B": np.array([15, 20, -20, 10, -5]),
"Product C": np.array([-5, 10, 15, -10, 25])}
fig, ax = plt.subplots(figsize=(7, 4.5))
pos_bottom = np.zeros(len(months))
neg_bottom = np.zeros(len(months))
for name, values in data.items():
bottom = np.where(values >= 0, pos_bottom, neg_bottom) # positives stack up, negatives stack down
ax.bar(months, values, bottom=bottom, label=name)
pos_bottom += np.clip(values, 0, None)
neg_bottom += np.clip(values, None, 0)
ax.axhline(0, color="black", linewidth=0.8)
ax.set_ylabel("Profit change (thousand USD)")
ax.legend(ncols=3, loc="lower center", bbox_to_anchor=(0.5, 1.0))
plt.tight_layout()
plt.show()
Tips for readable stacked bar charts
- Keep it to about five segments or fewer; with more, only the bottom segment is easy to compare.
- Put the most important or most stable series at the bottom, since it shares a common baseline.
- Use a consistent colour order in every chart, and a light-to-dark or diverging palette for ordered answers.
- Add totals on top when the total is the main message, and percentages when the mix is.
Continue with these related Matplotlib bar chart guides:
- Plot a bar chart in Matplotlib
- Show values on a Matplotlib bar chart
- Put the legend outside the plot
- Fix overlapping labels with tight_layout()
Frequently asked questions
How do I make a stacked bar chart in Matplotlib?
Draw the first series with plt.bar(x, a), then the next with plt.bar(x, b, bottom=a). For more series, keep a running NumPy array of the heights so far and pass it as bottom.
How do I create a stacked bar chart with a for loop?
Start with bottom = np.zeros(len(x)), and inside the loop call ax.bar(x, values, bottom=bottom) followed by bottom += values.
How do I add labels to a stacked bar chart?
Call ax.bar_label(bars, label_type="center") on each series for the segment values, and ax.bar_label() with custom labels= on the top series for totals.
How do I make a horizontal stacked bar chart?
Use ax.barh(categories, values, left=left) and increase left after each series. In pandas, use df.plot(kind="barh", stacked=True).
How do I plot a stacked bar chart from a pandas DataFrame?
Call df.plot(kind="bar", stacked=True). Each row becomes a bar and each column a stacked segment.
How do I handle negative values in a stacked bar chart?
Keep separate running totals for positive and negative values and use np.where(values >= 0, pos_bottom, neg_bottom) as the bottom of each segment.
Bijay Kumar is a 13-time Microsoft MVP with more than 18 years in software development, and the founder of Python Guides and TSinfo Technologies. He started out building .NET and SharePoint solutions at HP, TCS and KPIT before moving into Python, machine learning and AI, and he also builds web apps with TypeScript and React. He writes the tutorials here himself, and every example is run before publishing so you see the real output. More about Bijay · Microsoft MVP profile · LinkedIn