> "A graphic is not 'drawn' once and for all; it is 'constructed' and reconstructed until it reveals all the relationships constituted by the interplay of the data."
Learning Objectives
- Apply pre-attentive processing and Gestalt principles to guide the viewer's eye to the most important information
- Evaluate the data-ink ratio of a visualization and remove chartjunk without losing meaning
- Design visualizations that are accessible to viewers with color vision deficiency, low vision, and screen readers
- Identify and critique misleading visualization techniques including truncated axes, cherry-picking, and deceptive dual axes
- Redesign a flawed visualization by applying the principles from this chapter
In This Chapter
- Chapter Overview
- 18.1 How Humans See Charts: Perceptual Foundations
- 18.2 Data-Ink Ratio and Chartjunk
- 18.3 Color: The Most Powerful and Most Dangerous Encoding
- 18.4 Accessibility: Designing for Everyone
- 18.5 Aspect Ratio and Scale
- 18.6 Ethics: When Visualizations Lie
- 18.7 Common Design Mistakes (and How to Fix Them)
- 18.8 The Redesign Workflow
- 18.9 Annotation: Guiding the Reader
- 18.10 Putting It Together: A Design Checklist
- 18.11 Chapter Summary
Chapter 18: Visualization Design — Principles, Accessibility, Ethics, and Common Mistakes
"A graphic is not 'drawn' once and for all; it is 'constructed' and reconstructed until it reveals all the relationships constituted by the interplay of the data." — Jacques Bertin, Semiology of Graphics
Chapter Overview
In Chapters 15 through 17, you learned three powerful visualization libraries. You can create static plots with matplotlib, statistical graphics with seaborn, and interactive charts with plotly. You have the tools.
This chapter is not about tools. It is about judgment.
Every visualization you create embodies dozens of design decisions: which chart type, which colors, which axis range, which data to include, which to omit, how to label, where to annotate, what to title. Most of these decisions feel minor in the moment. But in aggregate, they determine whether your visualization communicates truth or distortion, whether it includes your entire audience or excludes people with disabilities, and whether it invites understanding or creates confusion.
This chapter gives you a framework for making those decisions well. We will cover three domains:
- Perceptual principles — how the human visual system processes charts, and how to design for it.
- Accessibility — how to ensure your visualizations work for people with color vision deficiency, low vision, and screen readers.
- Ethics — how to avoid (and detect) misleading visualization techniques.
This is the most conceptual chapter in Part III. There will be code — mostly "before and after" redesigns — but the emphasis is on developing your critical eye. By the end, you will be able to look at any chart and evaluate whether it is effective, accessible, and honest.
In this chapter, you will learn to:
- Apply pre-attentive processing and Gestalt principles to guide the viewer's eye to the most important information
- Evaluate the data-ink ratio of a visualization and remove chartjunk without losing meaning
- Design visualizations that are accessible to viewers with color vision deficiency, low vision, and screen readers
- Identify and critique misleading visualization techniques including truncated axes, cherry-picking, and deceptive dual axes
- Redesign a flawed visualization by applying the principles from this chapter
18.1 How Humans See Charts: Perceptual Foundations
Before you can design effective visualizations, you need to understand how the human visual system processes them. This is not abstract neuroscience — it has direct, practical implications for every design decision you make.
Pre-Attentive Processing
Your brain processes certain visual features before you consciously think about them. In under 250 milliseconds — faster than a blink — your visual system detects differences in:
- Color hue (a red dot in a field of blue dots)
- Color intensity (a dark dot in a field of light dots)
- Size (a large dot in a field of small dots)
- Orientation (a tilted line in a field of vertical lines)
- Shape (a circle in a field of squares)
- Position (a dot that breaks a pattern)
- Motion (a moving element in a static field)
This matters for visualization because elements encoded with pre-attentive features pop out — viewers notice them instantly without scanning the chart. Elements that differ only in ways the visual system does not process pre-attentively (subtle texture differences, small font changes) require effortful scanning.
Practical implication: When you want to highlight something in a chart — an outlier, a trend break, a key data point — encode it using a pre-attentive feature. Make it a different color. Make it larger. Add a bold annotation. Do not rely on the reader scanning every point to find it.
This also explains why "highlighting everything" is equivalent to highlighting nothing. If you make every data point a bright, saturated color, no single point pops out. The pre-attentive system needs contrast — one element must differ from the surrounding elements. The most effective visualizations have a visual hierarchy: a few highlighted elements that jump out, a larger set of supporting elements that provide context, and minimal decoration that stays in the background.
Consider two versions of the same scatter plot:
Before (no highlighting):
# All points same color, same size
sns.scatterplot(data=df, x="gdp", y="coverage",
color="gray", alpha=0.5)
After (key country highlighted):
sns.scatterplot(data=df, x="gdp", y="coverage",
color="lightgray", alpha=0.3)
# Highlight the specific country
target = df[df["country"] == "Rwanda"]
plt.scatter(target["gdp"], target["coverage"],
color="red", s=100, zorder=5)
plt.annotate("Rwanda", xy=(target["gdp"].iloc[0],
target["coverage"].iloc[0]),
fontsize=11, fontweight="bold",
xytext=(10, 10),
textcoords="offset points")
In the "before" version, Rwanda is one anonymous gray dot among hundreds. In the "after" version, it pops out instantly — red, larger, labeled. Pre-attentive processing makes the viewer's eye go there first.
Gestalt Principles
The Gestalt principles describe how the brain groups visual elements into coherent wholes. The most relevant for data visualization are:
Proximity: Elements that are close together are perceived as a group. This is why scatter plot clusters feel like categories even without color, and why adding space between groups of bars creates visual separation.
Similarity: Elements that look alike (same color, shape, size) are perceived as belonging together. This is why hue works — dots of the same color form a perceptual group even when spatially scattered.
Continuity: The eye follows smooth lines and curves. This is why line charts convey trends effectively — the brain naturally follows the line, perceiving the trajectory.
Enclosure: Elements enclosed by a boundary are perceived as a group. This is why faceted panels (surrounded by borders) feel like separate units, and why adding a background rectangle behind a legend groups those items together.
Connection: Elements connected by a line are perceived as related. This is why lines in a line chart imply continuity between data points, and why you should not use a line chart for unordered categorical data (the line implies an order that does not exist).
Practical implication: Use Gestalt principles intentionally. Group related elements by proximity or enclosure. Use consistent colors for the same category across all charts in a report. Avoid connecting unrelated points with lines.
The connection principle deserves special emphasis because it is frequently violated. When you connect data points with a line, you are telling the viewer "these points have a continuous, ordered relationship — the space between them is meaningful." This is appropriate for time series (the trend between data points is real) but inappropriate for categorical data (there is no "space between" Apples and Oranges). A startlingly common mistake is creating a line chart where the x-axis shows unordered categories. The resulting zigzag pattern implies trends that do not exist.
Cleveland and McGill's Hierarchy
In 1984, William Cleveland and Robert McGill published a landmark study ranking how accurately humans can decode different visual encodings. Their hierarchy, from most to least accurate:
- Position along a common scale (scatter plot, dot plot)
- Position along non-aligned scales (multiple panels with different y-axes)
- Length (bar chart)
- Angle (pie chart)
- Area (bubble chart)
- Color saturation / hue (choropleth, heatmap)
This hierarchy explains several design rules:
- Bar charts (length) are better than pie charts (angle) for comparing quantities. This is not opinion — it is perceptual science.
- Scatter plots (position) are better than bubble charts (area) for showing precise relationships.
- Choropleth maps (color) are effective for showing geographic patterns but poor for precise comparisons.
Practical implication: When you need precise comparisons, use position or length. When you need pattern overview, color and area are acceptable. Never ask the reader to compare areas or angles for exact values.
Let us apply this hierarchy to a common scenario. Suppose you want to show market share for five products. Here are your options, ranked by perceptual accuracy:
-
Dot plot or bar chart (position/length): The viewer can compare values to within 1-2% accuracy. They can instantly see which product leads and by how much.
-
Pie chart (angle): The viewer can tell which slices are larger or smaller, but comparing two similar slices (24% vs. 27%) requires conscious effort and is error-prone.
-
Bubble chart (area): The viewer can see the rough relative sizes, but comparing two bubbles with precision is nearly impossible. A bubble that is 50% more area than another does not look 50% larger — it looks about 25% larger because we underestimate area differences.
-
Treemap (area with rectangular shapes): Slightly better than circles because rectangular areas are easier to compare than circular ones, but still worse than length.
The hierarchy is not about what looks best — it is about what communicates most accurately. A pie chart might look more "professional" or "polished" to some audiences, but a bar chart is objectively more accurate for the task of comparing quantities. Design is about communication effectiveness, not aesthetic preference.
18.2 Data-Ink Ratio and Chartjunk
Tufte's Principle
Edward Tufte introduced the concept of data-ink ratio in his 1983 book The Visual Display of Quantitative Information:
Data-ink ratio = (ink used to display data) / (total ink used in the graphic)
The principle: maximize the data-ink ratio. Every pixel on your chart should either convey data or provide essential context (axis labels, legends). Remove everything else.
What Is Chartjunk?
Chartjunk is any visual element that does not contribute to understanding the data:
- Decorative gridlines — light gridlines help; heavy, numerous gridlines distract.
- 3D effects — adding depth to a 2D bar chart distorts bar lengths and adds no information.
- Gradient fills — making bars fade from dark to light adds visual complexity without encoding data.
- Background images — a photo behind your chart competes for attention.
- Unnecessary legends — if your chart has only one group, the legend is redundant.
- Redundant labels — if the y-axis says "Revenue (millions USD)" and every bar is also labeled "23M", one is redundant.
Before and After: Removing Chartjunk
Before (cluttered):
fig, ax = plt.subplots(figsize=(8, 5))
ax.bar(regions, values, color=["#ff6b6b",
"#ffd93d", "#6bcb77", "#4d96ff",
"#9b59b6", "#e67e22"],
edgecolor="black", linewidth=2)
ax.set_facecolor("#f0f0f0")
ax.grid(True, which="both", linewidth=1.5,
color="white")
ax.set_title("VACCINATION COVERAGE BY REGION",
fontsize=20, fontweight="bold",
color="navy")
ax.legend(["Coverage"]) # Unnecessary legend
for spine in ax.spines.values():
spine.set_linewidth(2)
This chart has: a colored background, heavy grid lines, bold thick borders, an all-caps screaming title, six different bar colors for a single variable (no meaning behind the color variation), thick bar edges, and a redundant single-item legend.
After (clean):
fig, ax = plt.subplots(figsize=(8, 5))
ax.bar(regions, values, color="#4d96ff",
width=0.6)
ax.set_title("Vaccination Coverage by Region",
fontsize=13, pad=12)
ax.set_ylabel("Coverage (%)")
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.set_ylim(0, 100)
The clean version has: one color (no misleading color variation), no background color, minimal spines, a readable title, and every remaining element serves a purpose. The data-ink ratio increased dramatically.
When Decoration Helps
Tufte's principle is a guideline, not a law. Sometimes "extra" elements serve legitimate purposes:
- Gridlines help when readers need to estimate exact values from the chart.
- Color variation is justified when it encodes a meaningful variable (not when each bar in a single-variable bar chart gets a different color for aesthetics).
- Annotations add context that the data alone cannot provide ("Policy changed here").
- Icons or images can help non-expert audiences identify categories (a flag for each country, for instance).
The test is: Does this element help the reader understand the data, or does it just fill space?
The Bateman Studies: When Decoration Helps
In 2010, Scott Bateman and colleagues published a study that challenged Tufte's minimalism. They found that "embellished" charts (with relevant visual metaphors and decoration) were more memorable than minimalist charts. Viewers remembered the data content better when the chart included thematic imagery.
This does not contradict Tufte's principle — it refines it. The key word is relevant. A chart about fishing data with a small fish illustration is a relevant embellishment that aids memory. A chart about tax revenue with a random gradient background is irrelevant decoration that distracts.
The practical takeaway: for exploratory analysis and scientific publications, lean toward minimalism (maximize data-ink ratio). For presentations, infographics, and public communication, thoughtful embellishment can improve engagement and recall — but only if the embellishment relates to the data's subject matter. Never add decoration just to fill space.
Applying the Data-Ink Ratio to Your Own Work
Here is a concrete process for improving any chart:
- Create the chart with default settings.
- Remove the top and right spines (
ax.spines["top"].set_visible(False)). - Reduce gridline weight or remove gridlines entirely.
- Remove any legend that is not needed (single-group charts, directly labeled charts).
- Check if all colors serve a purpose. If you have five bars but only one variable, use one color.
- Ask: if I removed this element, would the chart lose information? If no, remove it.
This process takes about 60 seconds and dramatically improves most charts. The goal is not artistic perfection — it is removing obstacles between the data and the viewer's understanding.
18.3 Color: The Most Powerful and Most Dangerous Encoding
Color is the visual encoding that data scientists abuse most often. It is powerful — it grabs attention, differentiates groups, and encodes continuous values. But it is also treacherous — it can exclude colorblind viewers, distort perceived differences, and carry cultural meanings you did not intend.
Three Types of Color Palettes
Sequential palettes map ordered data from low to high using a single-hue gradient (light-to-dark). Use for: continuous data with one direction (population density, temperature, percentage).
Examples: "Blues", "YlOrRd", "viridis"
Diverging palettes map data with a meaningful center point using two hues radiating from a neutral middle. Use for: data where both directions matter (profit/loss, above/below average, positive/negative correlation).
Examples: "coolwarm", "RdBu", "PiYG"
Qualitative palettes use distinct, unrelated hues for categorical data. Use for: unordered categories (regions, departments, product types).
Examples: "Set2", "tab10", "colorblind"
The Categorical Color Limit
The human eye can reliably distinguish about 7-10 colors in a chart. Beyond that, viewers struggle to match legend entries to data points. If your variable has more than 8-10 categories, consider:
- Grouping into fewer categories
- Using faceting (separate panels) instead of color
- Using direct labels instead of a legend
- Showing only the most important categories and grouping the rest as "Other"
The Rainbow Colormap Problem
The default "rainbow" or "jet" colormap — which cycles through red, orange, yellow, green, cyan, blue, violet — is one of the most widely used and most problematic colormaps in data visualization. Here is why:
-
Perceptual non-uniformity. Equal steps in data value do not produce equal steps in perceived color difference. The transition from green to cyan appears gradual; the transition from yellow to green appears abrupt. This makes some data differences look larger than they are and others smaller.
-
Colorblind inaccessibility. The red-green portion of the rainbow is invisible to the roughly 8% of men with deuteranopia (red-green color blindness).
-
False boundaries. The sharp hue transitions create perceived boundaries in the data that do not exist — viewers see "bands" of color and interpret them as distinct categories.
Better alternatives:
- "viridis" — perceptually uniform, colorblind-safe, works in grayscale
- "plasma" — similar properties, different aesthetic
- "cividis" — specifically designed for colorblind accessibility
# Before: problematic rainbow
sns.heatmap(data, cmap="jet")
# After: perceptually uniform
sns.heatmap(data, cmap="viridis")
Color and Cultural Meaning
Colors carry cultural associations that can interfere with data interpretation:
- Red often implies danger, loss, or negativity in Western cultures. Using red for "good" values can confuse readers.
- Green often implies success, growth, or positivity. A red-green diverging colormap carries these connotations whether you intend them or not.
- Blue is generally perceived as neutral or trustworthy across cultures, making it a safe default for single-color charts.
Be conscious of these associations when choosing colors. A chart showing profit in red and loss in green will confuse Western audiences. A chart showing temperature with blue for hot and red for cold will violate universal expectations. Match color connotations to data meaning, or use a palette without strong connotations (like "viridis").
Practical Color Guidelines
- One color for one meaning. If "AFRO" is blue in one chart, it should be blue in every chart in your report.
- Do not encode a variable with more than 7-8 colors. Beyond that, viewers cannot reliably distinguish or remember the mapping.
- Reserve bright, saturated colors for emphasis. Make most of the chart muted (grays, pastels) and use one vivid color for the element you want the reader to focus on.
- Test in grayscale. Print your chart on a black-and-white printer (or convert to grayscale digitally). If you can still distinguish all groups, the chart works for colorblind viewers and photocopies.
- Use seaborn's
"colorblind"palette as your default unless you have a specific reason not to.
18.4 Accessibility: Designing for Everyone
Approximately 300 million people worldwide have color vision deficiency. Roughly 4% of the global population has some form of visual impairment. An unknown but significant number of people access data through screen readers. Designing accessible visualizations is not a niche concern — it is a fundamental responsibility.
Color Vision Deficiency
The most common form is deuteranopia (difficulty distinguishing red from green), affecting about 8% of men and 0.5% of women of Northern European descent. Other forms include protanopia (reduced red sensitivity) and tritanopia (reduced blue sensitivity, rare).
Practical strategies:
- Never use color as the only encoding. If two groups are distinguished only by color (red vs. green), a colorblind viewer sees them as the same. Add a second encoding: shape, pattern, size, or label.
# Before: color only
sns.scatterplot(data=df, x="x", y="y",
hue="group")
# After: color + shape
sns.scatterplot(data=df, x="x", y="y",
hue="group", style="group")
-
Use colorblind-safe palettes. seaborn's
"colorblind"palette and plotly's default qualitative colors are designed for deuteranopia. For continuous data,"viridis","cividis", and"inferno"are safe. -
Test your charts. Several online tools simulate color vision deficiency. Upload a screenshot of your chart and see how it appears to a deuteranopic viewer. If two groups become indistinguishable, add a second encoding.
-
Use direct labels instead of legends. Instead of matching a color in the legend to a line on the chart (which requires distinguishing colors), place the label directly on or next to each line.
# Instead of relying on a color legend:
for region in regions:
subset = df[df["region"] == region]
ax.plot(subset["year"], subset["coverage"])
ax.text(subset["year"].iloc[-1],
subset["coverage"].iloc[-1],
f" {region}", fontsize=9,
va="center")
ax.legend().remove()
Contrast and Readability
- Text should have a contrast ratio of at least 4.5:1 against its background (WCAG AA standard). Light gray text on white backgrounds fails this test.
- Axis labels and tick marks should be large enough to read without squinting. When in doubt, increase font size.
- Thin lines (1 pixel wide) are hard to see for people with low vision. Use at least 1.5-2 point line width for data lines.
Alt Text for Charts
When charts appear on websites or in documents, they need alt text — a text description that screen readers can read aloud to blind users. Good alt text for a chart:
- States the chart type and what it shows: "Bar chart showing vaccination coverage by WHO region."
- Summarizes the key finding: "European and Western Pacific regions have the highest coverage at 93% and 91%. The African region has the lowest at 72%."
- Mentions the data source and date if relevant: "Source: WHO, 2023."
Bad alt text: "Chart" or "Figure 3" or "vaccination_bar_chart.png"
Good alt text: "Bar chart of mean vaccination coverage by WHO region in 2023. EURO leads at 93%, followed by WPRO at 91%, AMRO at 85%, EMRO at 82%, SEARO at 80%, and AFRO at 72%. Source: WHO."
Alt text is not a caption. A caption explains why the chart is there ("Figure 3 shows the regional disparities..."). Alt text describes what the chart looks like for someone who cannot see it.
Screen Reader-Friendly Data
For complex visualizations, alt text alone may not suffice. Consider providing:
- A data table alongside the chart, so screen reader users can access the exact values.
- A text summary that describes the key patterns, trends, and outliers.
- Structured headings that allow navigation (H2 for the chart title, followed by the alt text, followed by the data table).
The Legal and Institutional Dimension
Accessibility is not just a moral imperative — it is increasingly a legal requirement. In the United States, Section 508 of the Rehabilitation Act requires federal agencies to make electronic content accessible to people with disabilities. This includes data visualizations in government reports. The European Union's Web Accessibility Directive imposes similar requirements on public sector websites. Many universities and large companies have their own accessibility policies.
If you work in government, education, healthcare, or for any organization that receives federal funding, your visualizations may be legally required to be accessible. Even if they are not, making them accessible is simply good practice — it ensures your message reaches the widest possible audience.
A Before-and-After Accessibility Example
Let us take a concrete example. You have a line chart showing vaccination coverage over time for three regions:
Before (inaccessible):
fig, ax = plt.subplots()
ax.plot(years, afro, color="red")
ax.plot(years, euro, color="green")
ax.plot(years, searo, color="orange")
ax.legend(["AFRO", "EURO", "SEARO"])
Problems: red-green pair is invisible to deuteranopic viewers. No alt text. Legend requires color matching. Thin default lines.
After (accessible):
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(years, afro, color="#E69F00",
linewidth=2.5, marker="o", markersize=5)
ax.plot(years, euro, color="#56B4E9",
linewidth=2.5, marker="s", markersize=5)
ax.plot(years, searo, color="#009E73",
linewidth=2.5, marker="^", markersize=5)
# Direct labels instead of legend
ax.text(years[-1] + 0.3, afro[-1], "AFRO",
fontsize=10, fontweight="bold",
color="#E69F00", va="center")
ax.text(years[-1] + 0.3, euro[-1], "EURO",
fontsize=10, fontweight="bold",
color="#56B4E9", va="center")
ax.text(years[-1] + 0.3, searo[-1], "SEARO",
fontsize=10, fontweight="bold",
color="#009E73", va="center")
ax.set_title("EURO Leads in Coverage, AFRO "
"Shows Fastest Improvement")
ax.set_ylabel("Coverage (%)")
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
Improvements: Okabe-Ito colorblind-safe colors. Different marker shapes for each line (redundant encoding). Direct labels eliminate the need for legend matching. Thicker lines for visibility. Title states the key finding. Would need alt text when embedded in a document.
18.5 Aspect Ratio and Scale
How Aspect Ratio Distorts Perception
The same data can tell different visual stories depending on the aspect ratio:
- A wide, short chart compresses the y-axis, making trends look flat.
- A tall, narrow chart stretches the y-axis, making trends look steep.
There is no universal "correct" aspect ratio, but there are guidelines:
- For time series, William Cleveland's "banking to 45 degrees" principle suggests choosing an aspect ratio where the average slope of the line segments is approximately 45 degrees. This makes both upward and downward trends equally visible.
- For scatter plots, a roughly square aspect ratio works well when both axes have similar importance.
- For bar charts, the bars should be wide enough to be easily distinguishable but not so wide that they dominate the chart.
Truncated Y-Axes
One of the most common misleading practices is truncating the y-axis — starting it at a value other than zero. For bar charts, this is almost always deceptive because bar length encodes the value. A bar that starts at 90 instead of 0 makes a 5% difference look like a 500% difference.
Before (misleading):
fig, ax = plt.subplots()
ax.bar(["A", "B"], [92, 88])
ax.set_ylim(85, 95) # Truncated!
ax.set_title("A Is Dramatically Better Than B!")
The bars look vastly different. A appears more than twice as tall as B.
After (honest):
fig, ax = plt.subplots()
ax.bar(["A", "B"], [92, 88])
ax.set_ylim(0, 100)
ax.set_title("A and B Are Similar (92% vs 88%)")
Now the bars are nearly identical — because the data difference is small.
Exception: For line charts and scatter plots, a truncated y-axis is often appropriate. If your data ranges from 88 to 92, showing the full 0-100 range compresses all variation into an unreadable band at the top. The key distinction: bar length implies "this much of the whole," so the whole must start at zero. Line position implies "this is the value," so the scale should show the relevant range.
Rule: Zero-base bar charts. Use meaningful ranges for line and scatter plots. When in doubt, show both and let the reader decide.
Log Scales: When Linear Is Misleading
Sometimes a linear scale can be as misleading as a truncated axis. If your data spans several orders of magnitude (e.g., GDP per capita ranging from $500 to $100,000), a linear scale compresses all the low-GDP countries into a tiny range on the left while the high-GDP countries spread across the right. Differences between $500 and $5,000 (which might represent the difference between extreme poverty and emerging markets) become invisible.
A logarithmic scale treats multiplicative differences equally: the distance from $500 to $5,000 (10x) is the same as from $5,000 to $50,000 (10x). This is appropriate when proportional changes are more meaningful than absolute changes — which is true for most economic and demographic data.
# Linear scale: compresses low values
fig, (ax1, ax2) = plt.subplots(1, 2,
figsize=(12, 4))
ax1.scatter(df["gdp_per_capita"],
df["coverage_pct"], alpha=0.5)
ax1.set_title("Linear Scale")
ax1.set_xlabel("GDP per Capita (USD)")
# Log scale: reveals patterns at all scales
ax2.scatter(df["gdp_per_capita"],
df["coverage_pct"], alpha=0.5)
ax2.set_xscale("log")
ax2.set_title("Logarithmic Scale")
ax2.set_xlabel("GDP per Capita (USD, log)")
Always label log-scaled axes clearly. Readers unfamiliar with log scales may misinterpret the spacing between tick marks.
18.6 Ethics: When Visualizations Lie
Visualizations can mislead without containing a single false data point. The deception lies in the design choices, not the data. Here are the most common techniques — learn them so you can avoid creating them and detect them when others use them.
Cherry-Picking
Selecting a start date, end date, or subset of data that supports a predetermined conclusion.
Example: A company shows stock price from January to June (an upward trend) while omitting July to December (a crash). The chart is technically accurate — those data points are real — but the time window was chosen to mislead.
How to detect: Ask "Why this time range?" and "What happens if we expand the window?" Always check whether the visualization shows the full available data or a suspiciously convenient subset.
How to avoid: Show the full time range by default. If you must highlight a subset, show the full range in context (perhaps grayed out) with the subset highlighted.
Dual Y-Axes
Placing two different variables on the same chart with separate y-axes (one on the left, one on the right). This allows the creator to scale each axis independently, making any two trends appear correlated.
Why it is dangerous: By adjusting the scale of each axis, you can make any two lines cross, diverge, or track each other — regardless of the actual relationship. The viewer perceives the lines as comparable because they share the same x-axis and chart area, but the y-axes have different scales.
Better alternatives: - Use two separate panels stacked vertically with a shared x-axis. - Normalize both variables to a common scale (e.g., percentage change from baseline). - If you must use dual axes, label them prominently and use distinct visual encoding (solid line vs. dashed line, different colors) with clear annotations.
Area Distortion
When quantities are encoded as areas (circles, icons, silhouettes), the relationship between the data value and the visual size is easy to distort. If you double the radius of a circle, the area quadruples. If you double the height and width of a dollar-bill icon, the area quadruples. Viewers perceive area, not diameter or height, so a 2x data increase looks like a 4x visual increase.
How to avoid: When using bubble charts, ensure that the area (not the radius or diameter) is proportional to the data value. plotly and seaborn handle this correctly when you use size parameters, but custom charts built from scratch often get it wrong.
Misleading Map Projections
All world maps distort geography — it is mathematically impossible to flatten a sphere without distortion. The Mercator projection, the most common, dramatically inflates the area of regions far from the equator. Greenland appears the same size as Africa, when in reality Africa is 14 times larger. On a choropleth map, this means that high-latitude countries receive disproportionate visual weight.
How to mitigate: Use equal-area projections (like "natural earth" or "equal earth") for choropleth maps. When using Mercator (common in interactive maps like Google Maps), acknowledge the distortion in your caption or annotations.
Omitting Context
A chart showing that "our product's satisfaction score increased by 15 points!" is meaningless without context: - 15 points from what baseline? (50 to 65 is different from 80 to 95) - Over what time period? (one month vs. five years) - Compared to what? (competitors, industry average, previous year) - With what sample size? (15 respondents vs. 15,000)
How to avoid: Always include baselines, comparisons, time ranges, and sample sizes. Annotations and subtitles are your tools for context.
The Responsibility of the Visualizer
Every chart is an argument. By choosing what to show, what to hide, what to emphasize, and what to minimize, you are shaping the viewer's perception. This is not inherently dishonest — all communication involves choices. But it creates a responsibility: your choices should serve understanding, not manipulation.
A useful ethical test: Would I be comfortable explaining every design decision to a critical, knowledgeable audience? If you would be embarrassed to explain why you truncated the y-axis, cherry-picked the time range, or used a dual-axis chart, that is a signal that the design serves your narrative rather than the truth.
Simpson's Paradox in Visualization
One subtle form of misleading visualization involves aggregation. When you show aggregated data (overall mean, total count), you can obscure patterns that exist at the subgroup level — or create apparent patterns that vanish when you disaggregate. This is Simpson's Paradox.
For example, suppose a university's overall acceptance rate increased from 30% to 35% over five years. A bar chart of overall acceptance rate shows an upward trend. But when you break the data down by department, every department's acceptance rate decreased. The paradox occurs because the applicant pool shifted toward departments with higher base rates.
Visualization can both cause and solve this problem:
- Causing it: Showing only the aggregate bar chart hides the departmental decline.
- Solving it: Adding a faceted view by department (or a hue-encoded scatter) reveals the within-group trends.
The lesson: whenever you visualize aggregated data, ask yourself whether subgroup patterns might tell a different story. If the answer is "maybe," show both the aggregate and the disaggregated view.
The Spectrum from Honest to Deceptive
Not all misleading charts are equally harmful. It helps to think of a spectrum:
-
Accidental confusion: Default settings that happen to obscure the truth. The creator did not intend to mislead; they just did not know the defaults were problematic. Example: using a rainbow colormap that creates false boundaries.
-
Negligent omission: Failing to provide context that would change interpretation. The creator should have known better. Example: showing a "sharp increase" without mentioning it is within normal seasonal variation.
-
Strategic framing: Choosing design elements that favor a desired narrative while remaining technically accurate. The creator knows what they are doing. Example: truncating a bar chart axis to exaggerate a small difference.
-
Deliberate deception: Consciously designing a chart to create a false impression. Example: cherry-picking a time range and adding a misleading title to imply a trend that reverses when the full data is shown.
As a data scientist, your job is to operate at level 1 at worst (and fix accidental confusion when you spot it). Levels 3 and 4 are professional and ethical violations, even if no data is fabricated.
18.7 Common Design Mistakes (and How to Fix Them)
Mistake 1: Pie Charts for More Than 3-4 Categories
Pie charts encode values as angles, which humans perceive poorly (Cleveland and McGill's hierarchy, Section 18.1). With 7 categories, readers cannot tell whether a 14% slice is larger or smaller than a 16% slice.
Fix: Use a horizontal bar chart, sorted by value. The reader can compare bar lengths (position on a common scale) instead of angles.
Mistake 2: Using Color for Unordered Categories on a Map
Coloring a map by region using a sequential palette (light-to-dark) implies that regions have an order. "AFRO is darker than EURO" looks like AFRO is "more" of something.
Fix: Use a qualitative palette for categorical data on maps. Save sequential and diverging palettes for continuous variables.
Mistake 3: Line Charts for Categorical Data
Connecting categorical bars with lines implies continuity and order between categories. A line connecting "Apples" to "Oranges" to "Bananas" suggests a trend from apples to bananas, which is meaningless.
Fix: Use bar charts or dot plots for categorical data. Reserve line charts for ordered data (time series, ranked items).
Mistake 4: Too Many Chart Elements
A chart with 12 lines, 5 annotations, a legend with 12 entries, a title, a subtitle, axis labels, gridlines, and a data source note is visually overwhelming.
Fix: Apply the "squint test" — squint at your chart until the details blur. If you cannot identify the main message, the chart is too busy. Remove, simplify, or facet into multiple panels.
Mistake 5: No Title or No Axis Labels
A chart without a title forces the reader to figure out what they are looking at. A chart without axis labels forces them to guess the units.
Fix: Every chart needs a title that states what the chart shows. Every axis needs a label with units. This is the bare minimum for any visualization shared with others.
Mistake 6: Default Everything
Using all matplotlib defaults (no title, default axis labels, default figure size, default colors) produces technically correct but communicatively weak charts. Defaults are optimized for speed, not communication.
Fix: Spend 60 seconds on every chart: add a title, label axes with units, choose a meaningful color scheme, and adjust the figure size for the medium (notebook vs. slide vs. paper).
Mistake 7: Encoding One Variable with Two Channels
A bar chart where both the bar height and the bar color encode the same variable (taller bars are also darker) wastes a visual channel. The height already communicates the value; the color adds no new information.
Fix: Use the second channel (color) for a different variable, or remove it. If you have only one variable, use a single color for all bars.
Mistake 8: Inconsistent Color Across Charts
In a report with five charts, using blue for "AFRO" in one chart and red for "AFRO" in another forces the reader to re-learn the color mapping for each chart. This creates unnecessary cognitive load and can lead to misinterpretation.
Fix: Establish a consistent color mapping at the start of your analysis and use it throughout. In seaborn and plotly, you can define a color dictionary and pass it to every chart:
region_colors = {"AFRO": "#E69F00",
"EURO": "#56B4E9",
"SEARO": "#009E73",
"AMRO": "#CC79A7",
"EMRO": "#0072B2",
"WPRO": "#D55E00"}
# seaborn
sns.barplot(data=df, x="region", y="coverage",
palette=region_colors)
# plotly
px.bar(df, x="region", y="coverage",
color="region",
color_discrete_map=region_colors)
Mistake 9: Using a Legend When Direct Labels Would Work
Legends require the reader to look at the data, look at the legend, match the color, look back at the data, and keep the mapping in working memory. With more than 3-4 items, this back-and-forth becomes exhausting.
Fix: Place labels directly on or next to the data elements they describe. For line charts, label each line at its endpoint. For bar charts, place category names on the bars. For scatter plots with few groups, label the cluster directly. Remove the legend — it is no longer needed.
This technique is used by The New York Times, The Economist, and most high-quality data journalism. It is more work to implement (you need to position text carefully), but the result is dramatically more readable.
18.8 The Redesign Workflow
When you encounter a flawed visualization (your own or someone else's), use this systematic workflow to redesign it:
Step 1: Identify the Message
What question should this chart answer? Write it in one sentence. If you cannot, the chart may be trying to do too much.
Step 2: Audit the Encodings
For each visual element (position, color, size, shape, line style), ask: - What variable does this encode? - Is this the best encoding for this variable type (categorical vs. continuous)? - Does any encoding carry unintended meaning?
Step 3: Check Accessibility
- Simulate color vision deficiency. Are all groups distinguishable?
- Check contrast ratios for text.
- Write alt text. If you struggle to describe the chart's meaning in words, the visual message may be unclear.
Step 4: Check Honesty
- Does the y-axis start at zero for bar charts?
- Is the time range representative (not cherry-picked)?
- Are comparisons fair (same scale, same time period)?
- Is there missing context (baselines, sample sizes, uncertainty)?
Step 5: Remove Chartjunk
- Can any element be removed without losing information?
- Is the data-ink ratio as high as it can be while remaining readable?
Step 6: Add Annotations
- Is the main finding stated in the title or subtitle?
- Are key data points labeled?
- Is the data source cited?
Before and After: A Complete Redesign
Before: A 3D pie chart with 8 slices, a gradient background, rotated labels, and no title showing "Market Share by Product."
Problems: 3D distorts angle perception. 8 slices are too many for a pie chart. Gradient background is chartjunk. No title. Rotated labels are hard to read.
After redesign:
# Sort data, take top 5, group rest as "Other"
top5 = market_df.nlargest(5, "share")
other = pd.DataFrame({
"product": ["Other"],
"share": [market_df.nlargest(
5, "share", keep="all")
["share"].sum()]
})
plot_df = pd.concat([top5, other])
plot_df = plot_df.sort_values("share",
ascending=True)
fig, ax = plt.subplots(figsize=(8, 4))
ax.barh(plot_df["product"], plot_df["share"],
color="#4d96ff")
ax.set_xlabel("Market Share (%)")
ax.set_title("Product A Leads with 28% "
"Market Share (2023)")
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
for i, v in enumerate(plot_df["share"]):
ax.text(v + 0.5, i, f"{v:.0f}%", va="center")
The redesign: replaced pie with horizontal bar (better encoding), reduced to 6 categories (top 5 + Other), removed 3D and gradient, added a title that states the finding, added value labels, and cleaned up spines.
18.9 Annotation: Guiding the Reader
Annotations are the bridge between data and narrative. A well-annotated chart does not just show data — it tells the reader what to look at and why.
Types of Annotations
| Type | Purpose | Example |
|---|---|---|
| Title | States what the chart shows | "Vaccination Coverage by Region (2023)" |
| Subtitle | States the key finding | "African region lags 21 points behind Europe" |
| Axis labels | Identifies axes and units | "GDP per Capita (USD)" |
| Data labels | Shows exact values | "93%" on a bar |
| Callout | Draws attention to a specific point | Arrow pointing to an outlier |
| Reference line | Provides a benchmark | Horizontal line at 90% (herd immunity threshold) |
| Caption | Credits sources, explains methodology | "Source: WHO. N = 194 countries." |
The Annotation Hierarchy
Not all annotations are equal. Prioritize:
- Title and axis labels — mandatory for every chart.
- One key finding in the title or subtitle — the reader should know the main message without studying the chart.
- Reference lines or annotations for the most important comparison.
- Data labels on the most important data points (not all of them).
- Source attribution — builds trust and enables verification.
A common mistake is either no annotations (the reader is left to interpret alone) or too many annotations (the chart becomes a cluttered wall of text). Aim for the sweet spot: enough to guide, not so much as to overwhelm.
The Active Title
One of the highest-impact, lowest-effort improvements you can make to any chart is writing an active title — a title that states the finding, not just the topic.
Passive title (topic): "Vaccination Coverage by Region"
Active title (finding): "European Region Leads in Coverage; Africa Trails by 21 Points"
The passive title tells the reader what the chart is about. The active title tells them what the chart shows. With an active title, even a reader who barely glances at the chart understands the key message.
Not every chart needs an active title. For exploratory work, a descriptive title is fine. But for any chart you share with others — in a report, presentation, or dashboard — the active title is a gift to your reader. It saves them the work of figuring out the main point.
Annotation in Practice: The Annotated Scatter Plot
Let us annotate a GDP-coverage scatter plot as a complete example:
fig, ax = plt.subplots(figsize=(10, 6))
# Base scatter (muted)
ax.scatter(df["gdp_per_capita"],
df["coverage_pct"],
color="lightgray", alpha=0.5, s=20)
# Highlight outlier countries
outliers = df[df["country"].isin(
["Rwanda", "Cuba", "Equatorial Guinea"])]
colors = {"Rwanda": "#009E73",
"Cuba": "#56B4E9",
"Equatorial Guinea": "#E69F00"}
for _, row in outliers.iterrows():
ax.scatter(row["gdp_per_capita"],
row["coverage_pct"],
color=colors[row["country"]],
s=80, zorder=5)
ax.annotate(row["country"],
xy=(row["gdp_per_capita"],
row["coverage_pct"]),
xytext=(15, 10),
textcoords="offset points",
fontsize=9, fontweight="bold",
color=colors[row["country"]],
arrowprops=dict(arrowstyle="->",
color="gray"))
# Reference line
ax.axhline(90, color="red", linestyle="--",
alpha=0.5, linewidth=1)
ax.text(ax.get_xlim()[1], 90, " 90% target",
fontsize=9, color="red", va="bottom")
# Active title
ax.set_title("Rwanda and Cuba Achieve High "
"Coverage Despite Low GDP\n"
"Equatorial Guinea Is an "
"Underperformer for Its Income",
fontsize=12, pad=12)
ax.set_xlabel("GDP per Capita (USD)")
ax.set_ylabel("Coverage (%)")
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
# Source
ax.text(0.99, 0.01, "Source: WHO, 2023",
transform=ax.transAxes, fontsize=8,
ha="right", va="bottom", color="gray")
This chart uses nearly every annotation technique discussed: - A two-line active title stating both findings - Three highlighted outlier countries with arrows and labels - A reference line at the 90% target - Most data points shown in muted gray for context - Source attribution in the corner - Clean spines and clear axis labels
The result is a chart that communicates its message in seconds and rewards closer examination with additional detail.
18.10 Putting It Together: A Design Checklist
Use this checklist before sharing any visualization:
Message - [ ] The chart answers a specific question. - [ ] The title states what the chart shows (and ideally, the key finding). - [ ] A non-expert can understand the main message in 10 seconds.
Encoding - [ ] The chart type is appropriate for the data type and question. - [ ] Position and length are used for the most important comparisons. - [ ] Color is used purposefully, not decoratively. - [ ] No variable is encoded with two channels redundantly.
Accessibility - [ ] The palette is colorblind-safe (or a second encoding differentiates groups). - [ ] Text has sufficient contrast against the background. - [ ] Alt text is provided for web or document use. - [ ] Font sizes are readable at the intended display size.
Honesty - [ ] Bar chart y-axes start at zero. - [ ] The time range is representative, not cherry-picked. - [ ] Dual y-axes are avoided (or clearly labeled and justified). - [ ] Area encodings are proportional to values, not to radii or heights. - [ ] Missing context (sample size, uncertainty, baseline) is provided.
Polish - [ ] Chartjunk has been removed. - [ ] Axis labels include units. - [ ] The legend is clear and necessary. - [ ] The data source is cited. - [ ] The figure size and resolution are appropriate for the medium.
18.11 Chapter Summary
This chapter asked you to step back from the tools and think about the purpose, audience, and ethics of your visualizations. The key principles:
- Pre-attentive processing determines what viewers notice first. Use color, size, and position to highlight what matters.
- Gestalt principles govern how viewers group visual elements. Use proximity, similarity, and enclosure intentionally.
- Data-ink ratio measures the efficiency of your design. Remove elements that do not serve understanding.
- Color palettes must be chosen for the data type (sequential, diverging, qualitative) and tested for accessibility.
- Accessibility requires colorblind-safe palettes, sufficient contrast, multiple encodings, alt text, and data tables.
- Ethical visualization avoids truncated axes on bar charts, cherry-picked time ranges, misleading dual axes, and area distortion.
- Annotations guide the reader from raw data to insight. Every chart needs a title, axis labels, and a key finding.
You now have both the tools (matplotlib, seaborn, plotly) and the principles (perception, accessibility, ethics) to create visualizations that are effective, inclusive, and honest. The tools without the principles produce technically correct but potentially misleading charts. The principles without the tools are abstract ideals you cannot implement. Together, they make you a complete data visualizer.
The 60-Second Rule
Here is a practical test for any chart you create: show it to someone for 60 seconds, then take it away and ask them to describe what it shows. If they can state the main finding correctly, your chart communicates effectively. If they describe something other than what you intended, the chart needs work.
The 60-second rule is not about simplicity — complex charts can pass this test if they have a clear visual hierarchy, an active title, and well-placed annotations that guide the eye. Simple charts can fail this test if they lack labels, use confusing colors, or present data without context.
This rule also helps you determine whether your chart needs to be simplified or whether it needs better annotation. If the viewer says "I see a scatter plot with dots" but cannot identify the relationship or the key outliers, the problem is not complexity — it is guidance. Add an active title, highlight the key points, and annotate the finding. If the viewer says "there's too much going on, I don't know where to look," the problem is complexity — simplify by reducing the number of variables, groups, or panels.
Developing Your Critical Eye
The principles in this chapter are not things you learn once and then know forever. Developing a critical eye for visualization design is like developing good writing skills — it requires ongoing practice, exposure to good and bad examples, and a willingness to critique your own work.
Here are habits that will help you develop over time:
-
Collect examples. When you see a chart that communicates beautifully, save it. When you see a chart that misleads or confuses, save it. Over time, you will build a personal gallery that illustrates what works and what does not.
-
Critique before creating. Before building a chart, sketch it on paper. Decide on the chart type, the encodings, the color scheme, and the title before writing code. This prevents the common trap of accepting whatever the default looks like because you already invested time in the code.
-
Get feedback. Show your charts to people who are not data scientists. Ask them what the chart says. Their interpretation reveals communication gaps that you cannot see because you already know the data.
-
Read the experts. Tufte, Cairo, Wilke, and Cleveland wrote the books that define this field. Each offers a different perspective — Tufte on minimalism, Cairo on ethics, Wilke on practical guidelines, Cleveland on perceptual science. Together, they give you a comprehensive framework.
-
Apply the checklist. Every time you share a chart, run through the design checklist from Section 18.10. Over time, the checklist becomes automatic, and you will find yourself making better design decisions without consulting it.
Part III — Visualization — is now complete. In Part IV, you will turn to statistics, hypothesis testing, and machine learning, where visualization becomes your primary tool for understanding model behavior, checking assumptions, and communicating results. Everything you learned in these five chapters will be used constantly from here forward.