Exercises: Customization Mastery

These exercises are hands-on. Run them. Modify them. The difference between a matplotlib user who has "read about" customization and one who has "practiced" it is large, and it only closes with repetition.


Part A: Conceptual (6 problems)

A.1 ★☆☆ | Recall

State the four ways matplotlib accepts color specifications. Give an example of each.

Guidance (1) **Named colors**: `"steelblue"`, `"darkorange"`. (2) **Hex codes**: `"#1f77b4"`, `"#ff7f0e"`. (3) **RGB/RGBA tuples**: `(0.12, 0.47, 0.71)` or `(0.12, 0.47, 0.71, 0.5)` for 50% alpha. (4) **Grayscale strings**: `"0.5"`, `"0.8"` (floats 0-1 as strings for gray shades).

A.2 ★☆☆ | Recall

What is rcParams in matplotlib? Give two examples of settings that are commonly overridden.

Guidance `rcParams` is matplotlib's global configuration dictionary that holds every default setting. Commonly overridden: `font.family` (to use a deliberate font rather than DejaVu Sans), `figure.dpi` (for different display resolutions), `axes.spines.top` and `axes.spines.right` (to remove the top/right spines by default), `savefig.bbox` (set to "tight" for all saves), `pdf.fonttype` (set to 42 for publication PDF font embedding).

A.3 ★★☆ | Understand

Explain the difference between setting rcParams globally (mpl.rcParams[...] = ...) and using a context manager (with plt.rc_context({...}):). When would you prefer each?

Guidance Global rcParams affect every chart in the rest of the session until explicitly changed. Use when you want a consistent style for an entire script or notebook. Context managers apply settings only within the `with` block and revert afterward. Use when you need non-default styling for one specific chart without polluting the global state — for example, one dark-theme chart in a script that otherwise uses a light theme.

A.4 ★★☆ | Understand

The chapter's threshold concept says that "professional matplotlib is systematic, not ad hoc." Explain what this means and describe the two main tools for building a style system.

Guidance Systematic matplotlib means you do not customize each chart individually; you build a style system once and apply it automatically to every chart. The two main tools: (1) **Style sheets** — `.mplstyle` files or `plt.style.use(...)` calls that bundle rcParams into named themes that can be applied to a session. (2) **Reusable style functions** — Python functions like `apply_clean_style(ax)` that encapsulate customization logic and can be called on any Axes. Together, they reduce per-chart customization to near-zero while maintaining consistency across a body of work.

A.5 ★★☆ | Analyze

The chapter argues that the same rcParams can produce three different "house styles" for different contexts (academic, news, slide deck). Identify at least three rcParams settings that would differ across the three contexts, and explain why.

Guidance - **`font.size`**: small for academic (~9pt), medium for news (~11pt), large for slides (~16pt+). Reflects the size of the final display. - **`font.family`**: serif for academic (Times, Charter), sans-serif for news and slides (Inter, Helvetica). - **`figure.figsize`**: small for single-column journal (3.5 inches wide), larger for news (12 inches), largest for slides (14 inches). - **`figure.facecolor` and `axes.facecolor`**: white for academic and news, dark for slide theme. - **`text.color`, `axes.labelcolor`, tick colors**: black for light themes, white for dark theme. - **Line thickness**: thin (0.8) for academic, medium (1.5-2) for news, thick (3) for slides.

A.6 ★★★ | Evaluate

The chapter includes a "publication-ready checklist" in Section 12.12. In your own words, explain why the checklist is organized around Chapters 4, 6, 7, 8, and 12. What does each chapter contribute to the "is this chart ready?" question?

Guidance Chapter 4 (ethics) covers structural honesty — zero baseline, no cherry-picking, visible uncertainty. Chapter 6 (decluttering) covers removal of chart-junk and visual noise. Chapter 7 (typography and annotation) covers the words on the chart — action title, subtitle, axis labels with units, annotations, source attribution. Chapter 8 (composition) covers the aspect ratio and (for multi-panel) the layout. Chapter 12 (this chapter) covers the output settings — dpi, bbox, font embedding. Together, they cover the full pipeline from "correct data plotted correctly" to "ready to ship to an external audience."

Part B: Applied Customization (11 problems)

B.1 ★★☆ | Apply

Take the "ugly climate plot" from Chapter 10 Section 10.9 and apply the Chapter 6 declutter procedure through matplotlib method calls. Specifically: remove the top and right spines, lighten the remaining spines to gray, remove any heavy grid lines, and add light horizontal gridlines only. Save the result.

Guidance
fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(climate["year"], climate["anomaly"], color="#d62728", linewidth=1.5)

# Declutter
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["left"].set_color("gray")
ax.spines["bottom"].set_color("gray")
ax.spines["left"].set_linewidth(0.8)
ax.spines["bottom"].set_linewidth(0.8)

# Light horizontal gridlines
ax.grid(True, axis="y", linestyle="-", linewidth=0.5, color="#cccccc", alpha=0.6, zorder=0)
ax.set_axisbelow(True)

ax.set_title("Temperature Anomaly (Decluttered)")
ax.set_ylabel("Temperature Anomaly (°C)")
fig.savefig("climate_decluttered.png", dpi=300, bbox_inches="tight")
Compare the result side by side with the original from Chapter 10. The declutter produces a visible improvement without touching the data.

B.2 ★★☆ | Apply

Write a descriptive title for your decluttered climate chart and then rewrite it as an action title (per Chapter 7). Apply the action title with ax.set_title(..., loc="left", fontweight="semibold", fontsize=14, pad=12).

Guidance Descriptive: `"Temperature Anomaly, 1880-2024"`. Action: `"Global Temperatures Have Risen 1.2°C Above the 1951-1980 Baseline"`. The action title is longer but tells the reader the finding, not just the topic. Apply it with `ax.set_title("Global Temperatures Have Risen 1.2°C Above the 1951-1980 Baseline", loc="left", fontweight="semibold", fontsize=14, pad=12)`.

B.3 ★★☆ | Apply

Add annotations to the decluttered climate chart calling out the 2016 and 2023 record years. Use ax.annotate() with both xy (the target point) and xytext (the text position) and include an arrow.

Guidance
ax.annotate(
    "2023: +1.18°C record",
    xy=(2023, 1.18),
    xytext=(2005, 1.4),
    fontsize=10,
    color="#8b0000",
    arrowprops=dict(arrowstyle="->", color="gray", lw=0.6),
)

ax.annotate(
    "2016: +1.01°C",
    xy=(2016, 1.01),
    xytext=(2000, 1.22),
    fontsize=10,
    color="#8b0000",
    arrowprops=dict(arrowstyle="->", color="gray", lw=0.6),
)
The `xy` is the data point being annotated; `xytext` is where the text is placed. The arrow connects them. Adjust `xytext` if the text overlaps with other elements.

B.4 ★★☆ | Apply

Add an on-image source attribution to your climate chart using fig.text(...). Place it at the bottom left of the figure in small gray italic text.

Guidance
fig.text(
    0.125, 0.02,
    "Source: NASA Goddard Institute for Space Studies. Baseline: 1951-1980.",
    fontsize=8,
    color="gray",
    style="italic",
)
The coordinates `(0.125, 0.02)` are figure-relative — `(0, 0)` is the bottom-left of the figure, `(1, 1)` is the top-right. `(0.125, 0.02)` places the text near the bottom left with a small margin. Adjust these coordinates if the attribution does not appear where you want.

B.5 ★★☆ | Apply

Write a Python function apply_clean_style(ax) that takes a matplotlib Axes and applies the declutter principles: removes top/right spines, lightens the remaining spines, adds light horizontal gridlines, and sets tick parameters. The function should return the Axes.

Guidance
def apply_clean_style(ax):
    ax.spines["top"].set_visible(False)
    ax.spines["right"].set_visible(False)
    ax.spines["left"].set_color("gray")
    ax.spines["bottom"].set_color("gray")
    ax.spines["left"].set_linewidth(0.8)
    ax.spines["bottom"].set_linewidth(0.8)
    ax.tick_params(
        axis="both", which="major",
        labelsize=10, colors="gray",
        length=4, width=0.5, direction="out",
    )
    ax.grid(True, axis="y", linestyle="-", linewidth=0.5, color="#cccccc", alpha=0.6, zorder=0)
    ax.set_axisbelow(True)
    return ax
This function encapsulates the Chapter 6 declutter as a reusable one-liner for any future chart. Call it on every Axes you produce.

B.6 ★★☆ | Apply

Create a custom matplotlib style sheet as a .mplstyle file with at least 10 specific rcParams settings. Apply it with plt.style.use("/path/to/your/style.mplstyle") and verify that a default chart inherits the settings.

Guidance
# my_style.mplstyle
font.family: sans-serif
font.sans-serif: Inter, Helvetica, Arial
font.size: 11
figure.figsize: 10, 6
figure.dpi: 100
savefig.dpi: 300
savefig.bbox: tight
axes.spines.top: False
axes.spines.right: False
axes.titlesize: 14
axes.titleweight: semibold
axes.labelsize: 11
axes.grid: True
axes.grid.axis: y
axes.axisbelow: True
xtick.labelsize: 9
ytick.labelsize: 9
grid.color: cccccc
grid.linewidth: 0.5
grid.alpha: 0.6
pdf.fonttype: 42
ps.fonttype: 42
Save this as `my_style.mplstyle`. Apply with `plt.style.use("./my_style.mplstyle")`. Verify that a subsequent default chart shows the styling (no top/right spines, left-aligned titles, gray gridlines).

B.7 ★★☆ | Apply

Format a y-axis that shows values in millions of dollars with thousand-separators and a dollar sign prefix (e.g., $1,500`, `$15,000). Use matplotlib.ticker.StrMethodFormatter or FuncFormatter.

Guidance
from matplotlib.ticker import FuncFormatter

def dollars(x, pos):
    return f"${x:,.0f}"

ax.yaxis.set_major_formatter(FuncFormatter(dollars))
Or with StrMethodFormatter:
from matplotlib.ticker import StrMethodFormatter
ax.yaxis.set_major_formatter(StrMethodFormatter("${x:,.0f}"))
Both produce "$1,500", "$15,000", etc.

B.8 ★★☆ | Apply

Create a bar chart with five categories, and use ax.set_ylim(0, max_value * 1.1) to explicitly enforce Chapter 4's zero-baseline rule. Verify that the bars sit on a visible zero baseline.

Guidance
categories = ["A", "B", "C", "D", "E"]
values = [120, 85, 150, 95, 200]

fig, ax = plt.subplots(figsize=(8, 5))
ax.bar(categories, values, color="steelblue")
ax.set_ylim(0, max(values) * 1.1)
ax.axhline(0, color="gray", linewidth=0.8)
ax.set_title("Bar Chart with Explicit Zero Baseline")
ax.set_ylabel("Value")
Without `set_ylim(0, ...)`, matplotlib might auto-scale the y-axis to start slightly above zero, which distorts the bar length comparison.

B.9 ★★★ | Create

Write a reusable function style_publication(ax, title=None, subtitle=None, xlabel=None, ylabel=None, source=None) that applies full publication styling: the declutter from B.5, plus a left-aligned action title, optional subtitle, axis labels, and on-image source attribution.

Guidance
def style_publication(ax, title=None, subtitle=None, xlabel=None, ylabel=None, source=None):
    apply_clean_style(ax)  # from B.5

    if title:
        ax.set_title(title, fontsize=14, loc="left", fontweight="semibold", pad=12)
    if subtitle:
        fig = ax.get_figure()
        fig.text(0.125, 0.91, subtitle, fontsize=11, color="gray")
    if xlabel:
        ax.set_xlabel(xlabel, fontsize=11)
    if ylabel:
        ax.set_ylabel(ylabel, fontsize=11)
    if source:
        fig = ax.get_figure()
        fig.text(0.125, 0.02, source, fontsize=8, color="gray", style="italic")

    return ax
Use it:
fig, ax = plt.subplots(figsize=(12, 4))
ax.plot(years, anomaly, color="#d62728")
style_publication(
    ax,
    title="Global Temperatures Have Risen 1.2°C Since 1900",
    subtitle="NASA GISS surface temperature analysis",
    ylabel="Temperature Anomaly (°C)",
    source="Source: NASA GISS",
)

B.10 ★★★ | Create

Combine everything you have built in B.1-B.9 into a single script that loads the climate data, creates a decluttered chart, applies the publication style, adds annotations, and saves the result at dpi=300. This is the final polished version of the climate chart from Section 12.13.

Guidance Your final script should look something like Section 12.13's polished climate chart, with your custom `style_publication` function replacing the inline styling. The result should pass the 5-second test: a reader who has never seen the chart can understand what it shows, what the finding is, and where the data comes from.

B.11 ★★★ | Create

Save your polished climate chart in three different formats: PNG (dpi=300), SVG (for editing), and PDF (with pdf.fonttype=42 set). Open each in an appropriate viewer and verify that the fonts render correctly.

Guidance
import matplotlib as mpl
mpl.rcParams["pdf.fonttype"] = 42
mpl.rcParams["ps.fonttype"] = 42

# ... chart code ...

fig.savefig("climate.png", dpi=300, bbox_inches="tight")
fig.savefig("climate.svg", bbox_inches="tight")
fig.savefig("climate.pdf", bbox_inches="tight")
Verify: the PNG should be high-resolution (3600x1200 pixels or similar for a 12x4 inch figure at 300 dpi). The SVG should open in a vector editor and be fully scalable. The PDF should embed fonts correctly so the text looks identical to the PNG.

Part C: Synthesis and Style Systems (5 problems)

C.1 ★★★ | Evaluate

Take a chart you have previously made (any chart, from any project) and apply the publication-ready checklist from Section 12.12. For each item on the checklist, note whether your chart passes or fails. For any failures, describe the fix.

Guidance The point of this exercise is to develop a habit of systematic self-review. Most charts have 3-5 failures against the checklist. Common ones: descriptive title instead of action title, missing units, no source attribution, too many tick labels, default colors. For each failure, write the specific matplotlib code that would fix it.

C.2 ★★★ | Create

Design your own personal matplotlib style system. Write a brief document (under 500 words) describing: (a) the house style you want to apply by default (fonts, colors, spine treatment, etc.), (b) the matplotlib implementation (as a .mplstyle file, rcParams block, or style function), and (c) the contexts in which you would deviate from the default (e.g., slide decks, academic papers, web graphics).

Guidance The point is to commit to a consistent personal style. A short document helps you clarify your choices. Over time, you will refine the style based on what works — your first version is a starting point, not a final answer.

C.3 ★★★ | Create

Take the climate chart and produce three versions: a print journal version (single-column, serif, black-and-white), a news graphics version (wide, colored fill, action title), and a slide deck version (dark theme, large fonts). Use context managers and rc_context to isolate the styles.

Guidance Use Section 12.11's "Three Looks" examples as a starting point. Each version should be a self-contained block that produces a different saved file. At the end, you should have three PNG files showing the same climate data with three completely different visual styles.

C.4 ★★★ | Analyze

The chapter says matplotlib customization is "systematic, not ad hoc." Look at matplotlib code from a real-world data analysis project you have done (or find one on GitHub). Identify places where the code would benefit from systematizing: repeated customization blocks that could be factored into a function, ad-hoc styling choices that could be moved to a style sheet, etc. Propose a refactor.

Guidance Most real-world code has repeated customization patterns. Common ones: the same spine removal code applied to every chart (should become a function), the same figsize used everywhere (should become an rcParam), the same colors hardcoded across files (should become a shared palette). Identify one specific pattern and show how to factor it out.

C.5 ★★★ | Evaluate

The chapter's threshold concept says that professional matplotlib is systematic. Defend or critique this claim. Are there contexts where ad-hoc customization is actually better than a systematic style system? Where is the line between "appropriate customization" and "inconsistent code smell"?

Guidance The chapter's claim is probably right for most practitioners who produce many charts. But ad-hoc customization might be appropriate for one-off charts that will never be reproduced, for experimental charts that are still in flux, or for charts that intentionally break the house style for rhetorical effect. The line is judgment: a style system is a default, not a commandment, and deliberate deviations from the default are part of professional practice.

These exercises are hands-on. The payoff for doing them is large: once you have built a personal style function and a custom .mplstyle file, every subsequent chart you produce inherits your discipline automatically. Do at least five Part B exercises before moving on to Chapter 13.