Labels and Honesty
Overview
A chart without units is a decoration, and a cut axis is an argument.
What You Will Learn in This Lesson
By the end of this lesson, you will be able to:
- Say what every chart needs before it is readable.
- Explain what a truncated axis does to a comparison.
- State a comparison in words, with its size.
- Recognise a choice that is making an argument for you.
Why This Matters
Every chart makes a claim. The labels are what let a reader check it, and the axis is what decides how big the claim looks. Neither is a styling question: a chart with no units and a zoomed axis can make a one per cent difference look like a crisis, without a single number being wrong.
Step 1: The label is part of the answer
A number with no unit is not information. The reader cannot tell pounds from thousands of pounds from a percentage.
What a chart needs
A title saying what it shows, both axes labelled with units, and a note of how many observations are behind it. Four things, and a chart missing any of them is asking the reader to guess.
# title: "Mean exam score by team, 2024"
# y axis: "Mean score (out of 100)"
# x axis: "Team"
# note: "n = 48 students"
print("the units are half the claim")
The claim in words
If you cannot write the sentence, the chart is not finished. The sentence needs the direction, the size and the comparison.
import pandas as pd
df = pd.DataFrame({"team": ["red", "red", "blue"], "score": [90.0, 80.0, 70.0]})
means = df.groupby("team")["score"].mean().round(1)
gap = round(float(means.max() - means.min()), 1)
print(f"{means.idxmax()} leads on {means.max()}, {gap} ahead of {means.idxmin()}")
Mini Practice #1: Write the sentence
Try It YourselfProduce the means, then print a sentence naming the leader, its value and the gap.
Step 2: A truncated axis exaggerates
Starting a bar chart's axis at 80 rather than 0 makes a small difference fill the frame. It can be defensible; it is never neutral.
The same numbers, two impressions
85 and 82 differ by three and a half per cent. On an axis from 80 to 90 one bar is more than twice the other.
import pandas as pd
means = pd.Series({"red": 85.0, "blue": 82.0})
print(means)
print("real difference:", round(float(means.max() - means.min()), 1))
print("as a share of the smaller:", f"{(means.max() - means.min()) / means.min():.1%}")
full = means / 100
zoomed = (means - 80) / 10
print("bar heights on a 0-100 axis:", full.round(2).to_dict())
print("bar heights on an 80-90 axis:", zoomed.round(2).to_dict())
The numbers did not change. The picture did. If you truncate an axis, say so on the chart — and be sure the reason is that the detail matters, not that the difference looked small.
The other quiet arguments
A colour scale with an off-centre midpoint, a y-axis that is a rate in one panel and a total in the next, a time axis with uneven gaps. Each is a decision the reader will not see you make.
import pandas as pd
# Totals favour the big region; rates favour the small one.
df = pd.DataFrame({"region": ["big", "small"], "sales": [1000.0, 300.0], "people": [500, 50]})
df["per_person"] = df["sales"] / df["people"]
print(df)
print("by total:", df.loc[df["sales"].idxmax(), "region"])
print("by rate: ", df.loc[df["per_person"].idxmax(), "region"])
Mini Practice #2: How big is the difference, really
Try It YourselfPrint the absolute gap and the gap as a share of the smaller value, then decide whether it deserves a zoomed axis.
Step 3: When to Use It
Every chart you share
A title that states the finding, axis labels with units, and the data source.
Small differences that matter
A cut axis is acceptable when it is labelled clearly and the difference is the point.
When Not to Use It
Do not start a bar chart’s axis above zero. Bar length is the value, and a cut axis turns a 2% gap into what looks like double.
End-of-Lesson Exercises
Exercise 1: The headline sentence
Write headline(df) that returns a sentence of the form "red leads on 85.0, 15.0 ahead of blue", using the highest and lowest mean score per team.
Group to a mean per team, round to one place, then use idxmax and idxmin for the names. The gap is the difference between the two means.
Exercise 2: How big is the gap, in context
Write gap_size(df) returning a two-element list of [the absolute gap between the highest and lowest team mean, that gap as a percentage of the lowest mean], both to one decimal place. The second number is what says whether the first one matters.
Group to means, take max and min, subtract for the gap, then divide the gap by the minimum and multiply by 100. Round both at the end.