Skip to content

pandas plot.scatter

Two columns, one point per row.

Why does this one refuse to run without arguments, when .plot.line() and .plot.bar() are happy on their own? Because a scatter plot has no default. Line and bar both read the index for one axis and the columns for the other, so the shape tells them everything. A scatter plot has two axes and no index involvement at all — it needs to be told which column is horizontal and which is vertical, and there is no sensible guess.

So .plot.scatter(x='...', y='...') is the minimum, and every row of the frame becomes one point.

Official documentation: pandas.DataFrame.plot.scatter.

The arguments that earn their keep

df.plot.scatter(x='population', y='co2_per_capita')
df.plot.scatter(x='population', y='co2_per_capita', logx=True)   # skewed x
df.plot.scatter(x='a', y='b', alpha=0.3)     # see through overlapping points
df.plot.scatter(x='a', y='b', s='population')  # size by a third column
df.plot.scatter(x='a', y='b', c='continent')   # color by a third column
df.plot.scatter(x='a', y='b', figsize=(9, 7))

alpha is the one that rescues most real scatter plots. Anywhere past a few hundred points the marks pile up and a dense region looks identical to a moderately busy one; dropping opacity to 0.3 turns overlap back into information. logx and logy come next, because so many interesting quantities — population, income, company size — are spread across orders of magnitude, and on a linear axis every point but the outliers ends up in the corner.

A worked example, on real data

Population against emissions per person, one point per country:

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_per_capita', 'population'])

(df
 .loc[(df['year'] == 2023) & (df['iso_code'].str.len() == 3)]
 .dropna(subset=['co2_per_capita', 'population'])
 .plot.scatter(x='population', y='co2_per_capita', logx=True, alpha=0.5))

That gives 213 points. The distribution behind them explains both extra arguments:

count    213.00
mean       4.61
std        5.51
min        0.06
25%        0.99
50%        3.14
75%        5.76
max       40.13

Per-capita emissions run from 0.06 to 40 tonnes, and population from tens of thousands to over a billion. On linear axes almost every country sits crushed against the left edge while China and India occupy the rest of the canvas, so logx=True is doing the real work here. alpha=0.5 handles the cluster of small, low-emitting countries that would otherwise be one solid blob.

The dropna is not optional. plot.scatter silently skips rows where either coordinate is missing, so without it you get a chart drawn from an unknown number of countries.

Three mistakes people make

Forgetting that it drops missing rows quietly. Line charts leave a visible gap; scatter plots just have fewer points, and nothing tells you how many. Run .isna().sum() on both columns, or dropna(subset=[...]) explicitly, so the point count is a decision rather than an accident.

Plotting on linear axes out of habit. If a column's maximum is a thousand times its median, the linear chart is a picture of the outliers and nothing else. Check with .describe() before you draw.

Reading a shape as a cause. A scatter plot is very good at suggesting relationships that turn out to be population size in disguise. If both axes scale with something else, corr on the residuals is a better next step than another chart.

Where it shows up in Bamboo Weekly

Bamboo Weekly #49: Campaign finance is the method at its plainest — df.plot.scatter(x='TTL_DISB', y='TTL_RECEIPTS'), money in against money out, one point per campaign. When the two axes are the same kind of quantity, the interesting thing is the diagonal.

Bamboo Weekly #60: Iceland and Bamboo Weekly #72: City travel both build the frame at some length before scattering it — cleaning a price column in one case, stacking and joining in the other. That is typical: the scatter call is one line and the reshape above it is ten.

Practice it

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

Go deeper

corr puts a number on what the scatter plot suggests. plot.hist is worth drawing first on each axis, to see whether a log scale is needed. dropna makes the missing rows explicit. For hover labels telling you which country a point is, plotly express scatter is the better tool.

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 9 Bamboo Weekly exercises that use plot.scatter on real-world data — try each one, then study the worked solution.

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