Plotting¶
70 min Foundations Needs: Pandas
A chart is how you find the problems that info() and describe() cannot show
you. This page is about choosing the right one and reading it honestly, and then
about the two libraries you will draw it with.
Why this matters¶
Summary statistics hide shape. Two columns can share a mean, a standard deviation and a correlation and still look nothing alike when you plot them. This is the point of Anscombe's quartet: four datasets with nearly identical statistics, one a clean line, one a curve, one a line dragged by a single outlier. Nothing in the numbers tells you which you have.
There is a second reason, which arrives later and matters more. Somebody will eventually decide something because of a chart you made. Making that chart accurate is a technical skill. Making it honest is a separate one, and this page covers both.
How people read a chart¶
Not all visual channels are read equally well. Human beings judge position almost perfectly, length nearly as well, and everything after that badly.
This one ranking explains a surprising number of chart rules. It is why a bar chart beats a pie chart, because comparing bar heights is comparing lengths while comparing pie slices is comparing angles. It is why bubble sizes mislead, because area is read poorly and doubling a radius quadruples the area. And it is why colour belongs to categories or to a rough sense of high and low, never to a value somebody needs to read off precisely.
The rule that follows: put what matters most on a position or length channel. Push the rest to colour and shape.
Choosing a chart¶
Start from what you have and what you are asking, never from which chart looks impressive.
The six charts you actually need¶
Almost all exploratory work uses these. Learn them properly and the exotic ones can wait until you need them.
A few notes that save time later.
Histogram answers "what shape is this column". Change the number of bins and look again. Too few bins hide structure, too many turn the chart into noise, and the default is a guess rather than an answer.
Bar chart compares categories. Start the axis at zero, always, for the reason in the next section. Sort the bars by value unless the categories have a natural order, because a sorted bar chart answers "which is biggest" instantly.
Scatter is the workhorse for two numeric columns, and the first thing to reach for when you want to know whether a feature has any relationship with your target.
Line is for a value in order, usually time. If your x axis is a category rather than a sequence, you want bars. A line between unordered categories implies a trend that does not exist.
Box plot compares distributions across groups, and shows the median, quartiles and outliers at once. It is the fastest way to see whether a category actually separates your data.
Heatmap shows many pairs at once, most often correlations between every pair of numeric columns. Use a diverging colour scale centred at zero, so that positive and negative are visually opposite.
And the one to avoid: pie charts. They ask you to compare angles, which is near the bottom of the ladder. Two or three slices is survivable. More than that and a sorted bar chart is better in every way.
Charts that lie¶
Most misleading charts are not fraud. They are somebody reaching for a default without thinking about it.
The truncated axis. Bar charts encode value as length, so cutting the axis breaks the encoding: a bar twice as tall no longer means twice as much. Start bar charts at zero. Line charts are different, because they encode change rather than magnitude, so a truncated axis there is often the honest choice. Label it clearly either way.
Dual axes. Two lines with two different y scales can be made to cross, diverge or agree simply by choosing the scales. If you need two units on one chart, stack two charts instead.
Cherry-picked ranges. The same series over five years and over five months can tell opposite stories. Show the range that answers the question, and say what range you chose.
Colour that carries meaning it should not. Red for one group and green for another implies bad and good. Around one man in twelve cannot reliably tell those two apart anyway. Use a colourblind-safe palette and keep semantic colours for things that really are semantic.
ML connection
You will present model results to people who cannot read your code and will trust the picture. An accuracy of 94 percent on a chart with no baseline looks excellent until somebody notices that always guessing the majority class gives 93. Whether that baseline appears on the chart is a choice you make, and it is the same choice as where the axis starts.
When there is too much data¶
With enough points a scatter plot becomes a solid shape and stops carrying information. Three fixes, in order of how often they work:
- Make points smaller and semi transparent, so density shows as darkness.
- Sample a few thousand rows. You are looking for shape, not for every record.
- Switch to a chart that bins for you, such as a hexbin or a 2D histogram.
Part two: drawing it¶
Two libraries, and they are not rivals. Matplotlib is the engine and gives you control over every element. Seaborn sits on top of it, produces statistical charts in one line and has much better defaults.
The practical division: explore with seaborn, finish with matplotlib. Seaborn returns matplotlib objects, so you can always drop down a level.
Figure and axes¶
This is the concept that confuses every matplotlib beginner, and it takes one picture.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(df["month"], df["sales"])
ax.set_title("Monthly sales")
ax.set_xlabel("Month")
ax.set_ylabel("Sales (EUR)")
fig.savefig("sales.png", dpi=150, bbox_inches="tight")
You will also see the shorter style, plt.plot(...) followed by plt.title(...),
which draws on whichever figure happens to be current. It is fine for a quick
look and it falls apart the moment you have two charts. Learn the version above
and you never have to unlearn anything.
Several charts side by side:
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
axes[0].hist(df["salary"], bins=30)
axes[0].set_title("Salary distribution")
axes[1].scatter(df["age"], df["salary"], s=8, alpha=0.3)
axes[1].set_title("Age against salary")
fig.tight_layout()
tight_layout stops the labels of one chart overlapping the next one. Call it
before saving, every time.
Matplotlib goes much deeper than this page
Everything above is enough for the whole of trimester one. When you need custom axes, annotations or precise styling, the official quick start guide and the anatomy of a figure page are the two references worth bookmarking. Do not read them now. Read them the first time a chart will not do what you want.
Seaborn, one line each¶
import seaborn as sns
sns.histplot(data=df, x="salary", bins=30)
sns.countplot(data=df, x="city")
sns.scatterplot(data=df, x="age", y="salary", hue="city", alpha=0.4)
sns.lineplot(data=df, x="month", y="sales")
sns.boxplot(data=df, x="city", y="salary")
sns.heatmap(df.corr(numeric_only=True), annot=True, cmap="coolwarm", center=0)
Three arguments do most of the work. data takes the DataFrame, x and y take
column names as strings, and hue splits by a category and adds the legend for
you. That pattern is the same across every seaborn function, which is most of
why it is worth learning.
Set sensible defaults once, at the top of a notebook:
Finishing a chart¶
A chart is finished when somebody who did not make it can read it without asking you anything.
fig, ax = plt.subplots(figsize=(8, 5))
sns.boxplot(data=df, x="city", y="salary", ax=ax)
ax.set_title("Salary by city, 2026 survey")
ax.set_xlabel("City")
ax.set_ylabel("Monthly salary (EUR)")
fig.savefig("salary_by_city.png", dpi=150, bbox_inches="tight")
Note ax=ax, which tells seaborn to draw on the axes you created rather than
making its own. That is how the two libraries combine.
The checklist: a title that states the finding rather than the variables, axis labels with units, a legend when there is more than one series, and a source or date if the chart leaves your machine.
The charts you will make in every project¶
Five of them, from here to the end of the programme.
| Chart | What it tells you | When |
|---|---|---|
| Distribution of the target | Is it skewed, is it balanced | Before any modelling |
| Correlation heatmap | Which features move together | Feature selection |
| Missing value map | Whether gaps cluster in one group | Cleaning |
| Learning curve | Whether more data would help | Trimester two |
| Confusion matrix | Which classes get confused with which | Evaluation |
The first three you can draw today:
sns.histplot(data=df, x="target", bins=30)
sns.heatmap(df.corr(numeric_only=True), cmap="coolwarm", center=0)
sns.heatmap(df.isna(), cbar=False)
The missing value map is the one people skip. Gaps arranged in horizontal bands
mean whole rows failed, while a solid block in one column means a field stopped
being collected on a certain date. Both change what you should do about them,
and isna().sum() cannot tell them apart.
When it goes wrong¶
Nothing appears in my notebook
In a script you need plt.show() at the end. In Jupyter, put the plotting
call last in the cell, or add plt.show(), otherwise you may see only the
object's text representation.
My labels are cut off when I save the figure
Use fig.savefig("name.png", bbox_inches="tight"), and call
fig.tight_layout() before saving when you have several subplots.
Every chart appears on top of the previous one
You are using the plt. style and drawing into whichever figure is current.
Create a new one explicitly with fig, ax = plt.subplots() for each chart.
The x axis labels overlap and are unreadable
Either rotate them with ax.tick_params(axis="x", rotation=45) or swap the
axes so the categories run down the side, which is usually the better fix
when the names are long.
My scatter plot is a solid blob
Overplotting. Reduce s, set alpha=0.2, or sample the rows. See the
section above.
The correlation heatmap is all one colour
You are using a sequential colour map. Use a diverging one centred at zero,
such as cmap="coolwarm", center=0, so that positive and negative read as
opposites.
Check yourself¶
Use any dataset you have to hand.
- Plot one numeric column as a histogram with three different bin counts. Say what each one hides and what it reveals.
- Draw the same comparison as a bar chart and as a pie chart. Which one lets you rank the categories faster, and why does the encoding ladder predict that?
- Take a bar chart and redraw it with a truncated axis. Write one sentence describing the story each version tells.
- Make a scatter plot of two columns with at least a few thousand rows. Fix the overplotting and describe what became visible.
- Build a two panel figure with
plt.subplots, label both panels fully, and save it at 150 dpi with nothing cut off. - Draw the missing value heatmap for a real file. Say whether the gaps look random, and what you would do next.
Quick reference¶
| Task | Code |
|---|---|
| New figure | fig, ax = plt.subplots(figsize=(8, 5)) |
| Several panels | fig, axes = plt.subplots(1, 2, figsize=(12, 5)) |
| Histogram | sns.histplot(data=df, x="col", bins=30) |
| Counts per category | sns.countplot(data=df, x="col") |
| Bar of a value | sns.barplot(data=df, x="cat", y="val") |
| Scatter | sns.scatterplot(data=df, x="a", y="b", hue="c", alpha=0.3) |
| Line | sns.lineplot(data=df, x="t", y="v") |
| Box by group | sns.boxplot(data=df, x="cat", y="val") |
| Correlation heatmap | sns.heatmap(df.corr(numeric_only=True), cmap="coolwarm", center=0) |
| Missing values | sns.heatmap(df.isna(), cbar=False) |
| Draw into your axes | sns.boxplot(..., ax=ax) |
| Labels | ax.set_title(...), ax.set_xlabel(...), ax.set_ylabel(...) |
| Rotate ticks | ax.tick_params(axis="x", rotation=45) |
| Defaults | sns.set_theme(style="whitegrid", palette="colorblind") |
| Save | fig.savefig("f.png", dpi=150, bbox_inches="tight") |
Questions to sit with¶
- The encoding ladder says position and length are read most accurately. Name a chart you have seen recently that put the most important number on a weak channel. What was gained by doing that, and by whom?
- A truncated axis on a bar chart is misleading, but on a line chart it is often correct. What is the difference, in terms of what each chart encodes?
- You draw a chart, dislike the story it tells, and redraw it differently. When is that clarification and when is it manipulation? What would you record so that somebody else could tell?
- Anscombe's quartet exists because summary statistics hide shape. What is the equivalent risk when you report a single accuracy number for a model?
Next¶
You can see your data. Now the language for describing what you are seeing, and for saying how confident you should be about it.
Worth your time: the seaborn tutorial is short and well written, and the Data Visualisation Catalogue is a good browse when you are unsure which chart fits.