Skip to content

pandas plot.bar

One bar per row, one color per column.

How does Pandas decide what a bar chart's bars are? The same way it decides anything about .plot: by looking at the shape. Every row of the data frame becomes a position on the x axis, and every column becomes a series drawn at that position. With one column you get one bar per row. With four columns you get four bars per row, side by side — or four segments of one bar, if you pass stacked=True.

That makes the reshape the entire design decision. Choosing what goes in the index and what goes in the columns is choosing what the chart compares.

Official documentation: pandas.DataFrame.plot.bar.

The arguments that earn their keep

s.plot.bar()                          # a Series: one bar per index entry
df.plot.bar()                         # grouped bars, one color per column
df.plot.bar(stacked=True)             # segments of one bar instead
df.plot.bar(figsize=(10, 10))         # almost always needed for real labels
df.plot.bar(rot=0)                    # stop rotating the tick labels
df.plot.bar(y='co2')                  # one column out of many

stacked=True and figsize are the two that change outcomes. Stacking answers a different question from grouping — stacked shows the total and its composition, grouped shows the members against each other — so pick the one that matches the sentence you would write underneath. figsize matters more here than on a line chart, because bar charts carry a text label under every bar and the default canvas runs out of room fast.

A worked example, on real data

A Series is the simplest case, and it is worth starting there because the index does the labeling:

import pandas as pd

url = 'https://raw.githubusercontent.com/owid/co2-data/master/owid-co2-data.csv'

df = pd.read_csv(url, usecols=['country', 'iso_code', 'year', 'co2'])

(df
 .loc[(df['year'] == 2023) & (df['iso_code'].str.len() == 3)]
 .nlargest(8, 'co2')
 .set_index('country')['co2']
 .plot.bar())

The Series going in:

country
China            12172.0
United States     4918.0
India             3063.0
Russia            1733.0
Japan              987.0
Iran               790.0
Indonesia          762.0
Saudi Arabia       677.0

Eight rows, so eight bars, labeled from the index. The iso_code filter is doing quiet work: without it, "World", "Asia" and "High-income countries" would take the top places, because Our World in Data mixes aggregates and countries in the same column. Any bar chart of a country ranking needs that filter or it is a chart of nothing.

Note that set_index('country') is what produces the labels. Skip it and the bars are numbered 0 through 7, which is the single most common way a Pandas bar chart comes out unreadable.

Three mistakes people make

Leaving the labels to chance. Bars are labeled from the index, so the index has to be the thing you want written under each bar. If the chart shows integers along the bottom, you plotted a default RangeIndex and the names are still sitting in a column.

Stacking things that do not add up. stacked=True draws a total, which means the segments must be parts of one whole. Stacking percentages that each run to 100, or counts from overlapping categories, produces a bar whose height means nothing. Grouped bars are the honest default; stacking is the special case that has to be earned.

Plotting too many bars. A bar chart is a table you can look at, and it stops working somewhere around thirty bars. If the answer is "the top few", say so in the code with nlargest rather than drawing two hundred bars and hoping the reader finds them.

Where it shows up in Bamboo Weekly

Bamboo Weekly #66: Pittsburgh is the clearest progression on the site. It starts with pivot_table(index=df['CREATED_ON'].dt.year, columns='REQUEST_ORIGIN', aggfunc='count', values='REQUEST_ID').plot.bar(), then runs the same chain with stacked=True, figsize=(10, 10), then again after collapsing a family of similar categories with replace. Three versions of one chart, each fixing what the last one made obvious.

Bamboo Weekly #70: Moon missions counts launches per five-year bucket with pd.Grouper and plots the resulting Series directly, then switches to a pivot_table by operator to get plot.bar(stacked=True) — the same data, once as a total and once as a composition.

Bamboo Weekly #72: City travel sorts before it stacks: .groupby('Country')[['Active', 'Bus', 'Car']].mean() .sort_values('Car').plot.bar(stacked=True). Sorting a stacked bar chart by one of its segments is a small thing that makes the pattern legible.

Practice it

Work through a plot.bar exercise, with instant feedback and no signup required: practice.lernerpython.com/bamboo-weekly/plot-bar/

Go deeper

plot.barh is the same chart on its side, and the better choice whenever the category names are words. plot.line is for time rather than categories. pivot_table and groupby build the shape, and nlargest keeps the bar count honest. For a version a reader can hover over, see plotly express bar.

More Pandas videos on Python and Pandas with Reuven Lerner.

Bamboo Weekly is the practice. If you want the structured version — full courses with downloadable Jupyter notebooks, plus live Pandas office hours when you get stuck — that is LernerPython+Data. A paid Bamboo Weekly subscription is included with it.

See it on real data

Below are the 30 Bamboo Weekly exercises that use plot.bar on real-world data — try each one, then study the worked solution.

Part of the Pandas Methods Index. See also practice by skill.