5  Clarity in Data Communication

The first four chapters of this book focused on computation: loading, aggregating, combining, and reshaping data. This chapter shifts focus to communication. A DataFrame can be perfectly structured and a calculation perfectly correct, but if the resulting visualization or narrative is confusing, misleading, or cluttered, the analysis fails to achieve its purpose.

This chapter covers the principles of effective data visualization, introduces matplotlib’s core plotting interface, and addresses the fundamentals of writing clearly about data. You will learn to create basic plots, label them properly, annotate them with context, and begin developing the critical eye that distinguishes a helpful chart from a harmful one.

5.1 Setup

import numpy as np
import pandas as pd

%matplotlib inline
import matplotlib.pyplot as plt

pd.options.display.max_columns = 100

5.2 The Lifecycle of a Plot

Making a chart is not a single step but a cycle. You start with a question, choose a chart type, produce a draft, evaluate what is confusing or misleading, revise, and repeat. The first draft is almost never the final product, and that is fine. Treat visualization as an iterative design process, just as you would treat writing or coding.

The matplotlib Lifecycle of a Plot tutorial illustrates this principle well. A good plot is not born — it is built, step by step.

5.3 Visual Processing and Perceptual Rankings

Not all visual encodings are equally effective. Research by Cleveland and McGill, summarized in Schwabish’s Better Data Visualizations, shows that humans decode some visual channels more accurately than others. Ranked from most to least accurate for quantitative comparisons:

  1. Position along a common scale (points on a shared axis)
  2. Position along non-aligned scales
  3. Length
  4. Angle and slope
  5. Area
  6. Volume, color saturation, and color hue

This hierarchy explains why bar charts and dot plots (which encode data as position) tend to outperform pie charts (which encode data as angle and area) for precise comparisons. When you choose a chart type, you are choosing a perceptual channel — choose wisely.

TipThe data-ink ratio

Edward Tufte’s principle of maximizing the “data-ink ratio” — the proportion of ink devoted to data rather than decoration — is a useful heuristic. Gridlines, 3D effects, gradient fills, and elaborate legends often reduce clarity rather than improve it. Ask yourself: does this visual element help the reader understand the data, or does it just look pretty?

5.4 Making Your First Plot

Let’s build a visualization using the DIA passenger data from Chapter 4.

passenger_df = pd.read_csv('passengers.csv')
passenger_df['Month'] = pd.to_datetime(passenger_df['Month'])
passenger_df.set_index('Month', inplace=True)
passenger_df.drop(columns=['Cargo'], inplace=True)

A minimal line plot requires only one call.

passenger_df['Grand'].plot(figsize=(12, 6))

This default plot communicates the general trend, but it has several problems. There are no axis labels, no title, no indication of what the y-axis units are, and no context for the dramatic drop that is visible around 2020. Let’s improve it systematically.

5.5 Matplotlib’s Object-Oriented Interface

We will use matplotlib’s object-oriented coding style, which gives you explicit control over every element of your visualization. The key idea: you create a Figure (the canvas) and one or more Axes (the individual plots) using plt.subplots(), then call methods on those objects to customize them.

f, ax = plt.subplots(figsize=(12, 6))

passenger_df['Grand'].plot(ax=ax)

ax.set_xlabel('Date')
ax.set_ylabel('Monthly Passengers')
ax.set_title('Denver International Airport Total Passenger Traffic, 1995–2021')

The f object is the entire figure. The ax object is the subplot (called an “Axes” in matplotlib’s terminology). You will use ax.set_xlabel(), ax.set_ylabel(), ax.set_title(), and many other methods to customize the plot.

WarningUnlabeled axes are unacceptable

In this book and in professional practice, every chart must have labeled axes with units. A y-axis that says “Count” is better than nothing, but “Monthly Passengers” is better still. Include units (dollars, degrees Fahrenheit, percentage points) whenever applicable.

5.6 Adding Context with Annotations

Data does not exist in a vacuum. Major events — policy changes, natural disasters, pandemics — shape the patterns in your data. Annotations help the reader connect the visual pattern to the real-world cause.

f, ax = plt.subplots(figsize=(14, 6))

passenger_df['Grand'].plot(ax=ax, color='steelblue', linewidth=1.5)

# Vertical lines for major events
ax.axvline(pd.Timestamp('2001-09-01'), color='r', linestyle='--', linewidth=1, zorder=-1)
ax.axvline(pd.Timestamp('2020-03-01'), color='r', linestyle='--', linewidth=1, zorder=-1)

# Text annotations
ax.annotate('September 11', xy=(pd.Timestamp('2001-09-01'), 3_500_000),
            xytext=(pd.Timestamp('2003-06-01'), 4_500_000),
            arrowprops=dict(arrowstyle='->', color='red'),
            fontsize=10, color='red')

ax.annotate('COVID-19', xy=(pd.Timestamp('2020-03-01'), 1_000_000),
            xytext=(pd.Timestamp('2017-01-01'), 1_500_000),
            arrowprops=dict(arrowstyle='->', color='purple'),
            fontsize=10, color='purple')

ax.set_xlabel('Date')
ax.set_ylabel('Monthly Passengers')
ax.set_title('Denver International Airport Total Passenger Traffic, 1995–2021')

Now the chart tells a story: steady growth with a dip after 9/11, followed by strong recovery, then a historic collapse during COVID-19.

5.7 Multiple Subplots

You can create figures with multiple subplots by adjusting the nrows and ncols parameters of plt.subplots().

f, (ax0, ax1) = plt.subplots(
    nrows=1,
    ncols=2,
    figsize=(14, 5),
    sharey=True
)

passenger_df['United'].plot(ax=ax0, color='tab:blue')
ax0.set_title('United Airlines')
ax0.set_ylabel('Monthly Passengers')

passenger_df['Southwest'].plot(ax=ax1, color='tab:orange')
ax1.set_title('Southwest Airlines')

f.suptitle('DIA Monthly Passengers by Airline', fontsize=14)
f.tight_layout()

The sharey=True parameter ensures both subplots use the same y-axis scale, enabling fair comparison. The f.tight_layout() call adjusts spacing to prevent labels from overlapping.

5.8 Common Graphical Mistakes

As you develop your visualization skills, watch for these common pitfalls.

Truncated axes. Starting a bar chart’s y-axis at a value other than zero exaggerates differences. A bar that appears twice as tall should represent twice the value. Line charts are more forgiving because they emphasize change rather than magnitude, but be aware of the implicit argument you make when you zoom in on a narrow range.

Dual axes. Two y-axes on the same chart can imply a relationship between variables that does not exist. The apparent correlation between two lines can be manufactured by adjusting the scale of either axis. Use dual axes sparingly and with explicit justification.

Excessive decoration. Gridlines, 3D effects, gradient fills, and elaborate legends often reduce clarity. Every non-data element competes for the reader’s attention.

Missing context. A chart showing a dramatic upward trend is meaningless without knowing the baseline, the units, and the time period. Always include enough context for a reader to evaluate whether the pattern is meaningful.

Chart type mismatch. Using a pie chart to compare twelve categories, a bar chart to show a time series, or a scatter plot when you have only three data points — each of these forces the reader to work harder than necessary. Match the chart type to the structure of the data and the comparison you want to emphasize.

5.9 Writing About Data

Visualization is only half of data communication. The other half is writing. Good data writing, as Nolan and Stoudt argue in Communicating with Data, has several characteristics.

Lead with the finding. Do not make the reader sift through methodology to find the conclusion. State the key finding in the first sentence, then provide the evidence. “DIA passenger traffic fell 93% between February and April 2020” is a finding. “We analyzed monthly passenger data from 1995 to 2021” is a methodology statement — useful, but secondary.

Use specific numbers. “Traffic decreased significantly” is vague. “Traffic fell from 5.3 million to 380,000 passengers” is concrete and verifiable. Specificity builds trust.

Acknowledge uncertainty. If a result is based on a sample, a model, or incomplete data, say so. Your credibility depends on being honest about what you know and what you do not know. We will explore uncertainty communication further in Chapter 11.

Write for your audience. A blog post for the general public requires different vocabulary and level of detail than a technical report for data scientists. We will explore audience analysis in Chapter 8.

5.10 Crafting Sentences

Some specific writing advice for data communication, drawn from Schwabish, Nolan, and Stoudt.

Active voice over passive. Write “Colorado legalized cannabis in 2014” rather than “Cannabis was legalized in Colorado in 2014.” Active voice is shorter, clearer, and assigns agency.

Avoid hedging where the data is clear. If the result is statistically significant, say “spending differed between parties” rather than “spending may have differed between parties.” Reserve hedging for genuinely uncertain claims.

Define jargon on first use. If you must use a technical term, define it immediately. “The p-value (the probability of seeing this result if there were no real difference) was less than 0.001.”

Keep sentences short. If a sentence contains more than one data point, consider splitting it. “DIA served 5.3 million passengers in February 2020. By April, that number had fallen to 380,000.”

5.11 Exercises

Find a data visualization in a news article, social media post, or textbook. Identify three specific things the visualization does well and three things it could improve. Consider labels, scales, color choices, clutter, and whether the chart type matches the data.

Solution:

There is no single correct answer. The goal is to practice articulating why a design choice helps or hinders communication. Strong critiques reference the perceptual ranking hierarchy: “The pie chart uses angle to encode values, which is harder to read accurately than position. A bar chart would make the comparison clearer.”

Load any dataset from the book’s repository. Create a default plot using .plot(). Then create a second version with at least five improvements: axis labels, a title, annotations, color choices, and a figsize adjustment. Compare the two.

Solution:

deaths_df = pd.read_csv('CDC_deaths.csv', parse_dates=['Week date'])

# Default
deaths_df.groupby('Year').agg({'Diseases of heart': 'sum'}).plot(kind='bar')
# Improved
f, ax = plt.subplots(figsize=(10, 5))
totals = deaths_df.groupby('Year').agg({'Diseases of heart': 'sum'})
totals.plot(kind='bar', ax=ax, color='indianred', legend=False)
ax.set_xlabel('Year')
ax.set_ylabel('Total Deaths')
ax.set_title('Annual U.S. Heart Disease Deaths, 2014–2023')
ax.set_xticklabels(totals.index, rotation=45)
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)

Removing the top and right spines reduces visual clutter and follows Tufte’s data-ink ratio principle.

Take the improved chart from Exercise 2 and write a three-sentence summary that leads with the finding, provides one supporting number, and notes a caveat.

Solution:

Example: “Heart disease deaths in the U.S. increased 5.4% from 2019 to 2020, the largest year-over-year jump in the past decade. Total deaths rose from approximately 657,000 to 692,000, a change that coincides with the onset of the COVID-19 pandemic. This increase likely reflects both direct cardiovascular effects of COVID-19 and disruptions to routine cardiac care, though the CDC data does not distinguish between these mechanisms.”

You want to compare the total number of passengers carried by each airline at DIA. Rank the following chart types from most to least effective for this comparison: pie chart, horizontal bar chart, bubble chart, table of numbers. Explain your reasoning using the Cleveland and McGill hierarchy.

Solution:

  1. Horizontal bar chart — encodes values as position/length along a common scale (top of the hierarchy).
  2. Table of numbers — exact values with no perceptual encoding required; excellent for precision but poor for quick pattern recognition.
  3. Bubble chart — encodes values as area, which humans judge less accurately than length.
  4. Pie chart — encodes values as angle and area, the least accurate channels in the hierarchy. Particularly poor when there are many slices of similar size.

5.12 Looking Ahead

With a foundation in both data shaping and visualization principles, you are ready to start building specific chart types. In Chapter 6, you will take a deep dive into matplotlib’s object-oriented interface by visualizing distributions with histograms — your first systematic encounter with the shape of data values themselves.