Create a simple line plot using the Pandas API, with one line per column.
Why does the same call produce a beautiful chart on one data frame and forty unreadable lines on another? Because .plot.line() does not ask what you want plotted. It plots every column as a line, using the index as the x axis, and that is the whole specification. Get the shape right and it needs no arguments at all; get it wrong and no argument will save it. Almost everything you will ever do to fix a Pandas line chart happens before the word plot.
It is an accessor rather than a method, so it hangs off .plot, and it draws through matplotlib — which means a static image, not the hoverable chart you get from plotly express line. The trade is speed of typing against everything else, and inside a notebook that is often the right trade for a first look.
Official documentation: pandas.DataFrame.plot.line.
The arguments that earn their keep
df.plot.line() # every column a line, index as x
df.plot.line(y='co2') # just one column
df.plot.line(x='year', y='co2') # name the x column instead of the index
df.plot.line(subplots=True) # one panel per column, not one legend
df.plot.line(logy=True) # log scale, for anything compounding
df.plot.line(figsize=(12, 6)) # when the default is too cramped
df.plot.line(title='CO2, Mt')
figsize and subplots are the two you will reach for most, and both are answers to the same problem: too much on one pair of axes. Everything else is cosmetic. Note that x= is available but rarely used, because if a column belongs on the x axis it usually belongs in the index — and once it is in the index, sorting, slicing and joining all get easier too.
A worked example, on real data
Our World in Data's CO2 file is one row per country per year, which is exactly the wrong shape for a line chart: plot it as it comes and you get one line per column, and the columns are country, year and co2. The data has to be turned inside out first, so that each country becomes a column and the year becomes the index:
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'])
g7 = ['United States', 'China', 'India', 'Germany',
'Japan', 'United Kingdom', 'France']
(df
.loc[(df['year'] >= 1990) & (df['country'].isin(g7))]
.pivot(index='year', columns='country', values='co2')
.plot.line())
The frame handed to .plot.line() looks like this:
country China France Germany India Japan United Kingdom United States
year
2022 11711.8 295.3 667.8 2831.1 1029.6 311.1 5055.4
2023 12172.0 270.3 593.8 3062.8 986.9 307.8 4918.4
2024 12289.0 264.2 572.3 3193.5 961.9 312.9 4904.1
Seven columns, so seven lines; a year index, so a year axis. The chart labels itself — the legend comes from the column names and the x axis title from the index name — and none of that required an argument. It required a pivot.
That is the pattern worth memorizing. pivot, pivot_table, unstack and .T all exist to turn long data into wide data, and a line chart is the most common reason to want it.
Three mistakes people make
Plotting long data as it comes. One row per observation with a column naming the series is the right shape for storage and the wrong shape for .plot.line(). You get a line for every numeric column instead of a line for every series, which on the CO2 file means a line called "year" climbing steadily from 1990 to 2024. If the legend names things that are not series, you skipped the reshape.
Expecting an unsorted index to work. The line is drawn in row order, not index order, so an unsorted index produces a chart that doubles back on itself — a scribble rather than a trend. groupby sorts for you, pivot sorts for you, read_csv does not. Call .sort_index() before plotting anything whose index came straight from a file.
Forgetting that gaps break the line. A missing value leaves a hole, and enough holes leave a dotted mess. Decide deliberately between ffill and interpolate rather than letting matplotlib pick — and if a series is mostly missing, the honest chart is the one that shows the gaps.
Where it shows up in Bamboo Weekly
Bamboo Weekly #68: Dangerously hot weather is the simplest form there is — df['All Hazard Damages (M)'].plot.line() on a single Series, no reshape, no arguments. Worth seeing, because it is a reminder that the ceremony below is about shape, not about plotting.
Bamboo Weekly #75: Refugees and Bamboo Weekly #81: School spending both end in .T.plot.line(). In each case the countries were already the index and the years were already the columns, so a transpose was all the reshaping needed. .T is the cheapest pivot there is when the data is already a clean rectangle.
Bamboo Weekly #79: Cyber attacks groups by year and month before plotting — groupby([df['event_date'].dt.year, df['event_date'].dt.month])['slug'].count().plot.line(). The result is a Series with a two-level index, which plots as a single line with compound tick labels.
Bamboo Weekly #60: Iceland shows the full sequence in one chain: pivot_table, then interpolate to close the gaps, then .plot.line(). That middle step is the one people skip.
Practice it
Work through a plot.line exercise, with instant feedback and no signup required: practice.lernerpython.com/bamboo-weekly/plot-line/
Go deeper
plot.bar is the same accessor for categories rather than time, and plot.scatter is the one that breaks the pattern by requiring x= and y=. pivot_table and unstack are how you get the shape this method wants. When the chart is for a reader rather than for you, plotly express line gives them hover, zoom and a clickable legend for the same one call.
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 63 Bamboo Weekly exercises that use plot.line on real-world data — try each one, then study the worked solution.
- Bamboo Weekly #131: Canadian border crossings
- Bamboo Weekly #128: Extreme heat
- Bamboo Weekly #125: Shrinking dollars
- Bamboo Weekly #122: Economic growth
- Bamboo Weekly #117: Electricity
- Bamboo Weekly #116: Philadelphia Fed survey
- Bamboo Weekly #113: US airport traffic
- Bamboo Weekly #110: Credit access
- Bamboo Weekly #108: Measles
- Bamboo Weekly #107: Consumer confidence
- Bamboo Weekly #106: Flu season
- Bamboo Weekly #103: CDC data
- Bamboo Weekly #102: WordPress
- Bamboo Weekly #100: Sports betting
- Bamboo Weekly #99: Literacy and numeracy
- Bamboo Weekly #98: Retail sales
- Bamboo Weekly #97: Drones
- Bamboo Weekly #95: Tariffs
- Bamboo Weekly #93: Anti-politics
- Bamboo Weekly #92: Climate disaster costs
- Bamboo Weekly #89: Housing
- Bamboo Weekly #84: Central banks
- Bamboo Weekly #83: Gasoline prices
- Bamboo Weekly #81: School
- Bamboo Weekly #79: Cyber attacks
- Bamboo Weekly #75: Refugees
- Bamboo Weekly #73: Avocado hand
- Bamboo Weekly #68: Dangerously hot weather
- Bamboo Weekly #65: Microplastics
- Bamboo Weekly #64: Coal power
- Bamboo Weekly #63: Ukraine aid
- Bamboo Weekly #60: Iceland
- Bamboo Weekly #59: Long covid
- Bamboo Weekly #58: NATO
- Bamboo Weekly #57: International arms trade
- Bamboo Weekly #56: Rent increases
- Bamboo Weekly #54: Household debt
- Bamboo Weekly #53: Airport animals
- Bamboo Weekly #52: Border encounters
- Bamboo Weekly #51: Academy Awards
- Bamboo Weekly #50: Red Sea shipping
- Bamboo Weekly #48: Aviation accidents
- Bamboo Weekly #47: Minimum wage
- Bamboo Weekly #46: Pedestrians
- Bamboo Weekly #45: Netflix
- Bamboo Weekly #43: Financial protection
- Bamboo Weekly #41: Wine production
- Bamboo Weekly #39: WeWork
- Bamboo Weekly #37: Consumer finances
- Bamboo Weekly #35: Terrorism
- Bamboo Weekly #33: Fracking
- Bamboo Weekly #32: Unions
- Bamboo Weekly #31: Poverty
- Bamboo Weekly #30: Uncertainty
- Bamboo Weekly #29: Auto accidents
- Bamboo Weekly #26: Hot weather
- Bamboo Weekly #24: Wildfire smoke
- Bamboo Weekly #23: Misery index
- Bamboo Weekly #20: World inflation
- Bamboo Weekly #18: World population
- Bamboo Weekly #16: Consumer oil prices
- Bamboo Weekly #14: JOLTS
- Bamboo Weekly #11: Software jobs
Part of the Pandas Methods Index. See also practice by skill.