7  Visualization with Integrity

7.1 Learning objectives

After completing this unit, you will be able to:

  • begin a visualization with a clearly stated question;
  • build a graph progressively using fig and ax, followed by plot, scatter, and errorbar;
  • map variables to positions, markers, lines, and intervals without hiding their units;
  • explain how axis truncation and aspect ratio change the visual impression;
  • display uncertainty without assigning it an unspecified statistical meaning;
  • provide data, figures, and textual descriptions that can be checked again; and
  • distinguish patterns in a figure from general mathematical proof.

Local prerequisites: Unit 1, Cartesian coordinates, equations of straight lines, ratios, and intervals at the A30 level. This unit does not require calculus.

7.2 A figure begins with a question

A graph is not decoration added after a calculation is complete. It is a structured argument about data. Before choosing colors or line styles, state the question.

In this unit, the question is:

Are seven distance measurements consistent with the model d=2t+1d=2t+1, if each measurement has an uncertainty of ±0.4\pm 0.4 meters?

The dataset is deliberately small so that every mark on the graph can be matched to the table.

Time tt (s) Measured distance dd (m) Uncertainty (m) Model value (m)
0 1.0 0.4 1.0
1 3.1 0.4 3.0
2 4.8 0.4 5.0
3 7.2 0.4 7.0
4 9.0 0.4 9.0
5 10.9 0.4 11.0
6 13.1 0.4 13.0

Because the question compares measurements with a model, the graph must show both. Showing only the model line would remove the measurement evidence; showing only the points would hide the object of comparison.

7.3 Encoding: what means what?

Every visual element must have a meaning that can be written down without looking at the figure.

Visual element Meaning
horizontal position time tt, in seconds
vertical position distance dd, in meters
circular points measured values
vertical bars measurement intervals d±0.4d\pm0.4 meters
dashed line model values d=2t+1d=2t+1

Color helps distinguish elements, but it must not be the only encoding. Circular points and a dashed line remain distinguishable in grayscale print or for readers who cannot distinguish particular colors.

Choose an encoding that fits the question. Position on a scaled axis is useful for comparing quantities. A pie chart is unsuitable here: distances at different times are not parts of a single whole.

7.4 Progressive lab: from fig and ax to a complete graph

Matplotlib separates the whole figure (fig) from the coordinate region (ax). fig controls the canvas that will be saved. ax receives the data, scales, labels, and legend. This separation makes each decision visible and testable. The following lab begins with an empty canvas, then adds exactly one layer at each stage.

7.4.1 Stage 1 - create fig and ax

Listing 7.1
from matplotlib import pyplot as plt
from source.code.unit04_visualization import ALT_TEXT, OBSERVATIONS

fig_u04_lab, ax_u04_lab = plt.subplots(figsize=(8.0, 4.8))

Variable names may differ, but the pattern fig, ax = plt.subplots(...) is worth retaining: someone reading the code can immediately see which object will be saved and which receives the data encoding. The figure size is fixed so that it does not depend on the interactive window size.

Prepare four data sequences from the table. The lists follow the already validated observation order.

Listing 7.2
times_u04 = [observation.time_s for observation in OBSERVATIONS]
measured_distances_u04 = [
    observation.measured_distance_m for observation in OBSERVATIONS
]
uncertainties_u04 = [observation.uncertainty_m for observation in OBSERVATIONS]
model_u04 = [observation.model_distance_m for observation in OBSERVATIONS]

7.4.2 Stage 2 - plot for the model

ax.plot suits model values ordered by time. The dashed line provides a second encoding in addition to color.

Listing 7.3
model_line_u04, = ax_u04_lab.plot(
    times_u04,
    model_u04,
    color="#D55E00",
    linestyle="--",
    linewidth=2,
    label="Model d = 2t + 1",
)
assert model_line_u04.get_linestyle() == "--"

The comma in model_line_u04, = ... unpacks the one-element list of lines returned by plot. At this stage, there are no measurement points or uncertainty bars yet.

7.4.3 Stage 3 - scatter for measurements

ax.scatter adds the seven measurements as separate markers. Their circular shape keeps the series recognizable without color.

Listing 7.4
measurement_points_u04 = ax_u04_lab.scatter(
    times_u04,
    measured_distances_u04,
    marker="o",
    s=36,
    color="#0072B2",
    label="Measurements",
    zorder=3,
)
assert len(measurement_points_u04.get_offsets()) == 7

7.4.4 Stage 4 - errorbar for uncertainty

The uncertainty bars form a separate layer. fmt="none" prevents errorbar from drawing a second set of points over the scatter output.

Listing 7.5
uncertainty_bars_u04 = ax_u04_lab.errorbar(
    times_u04,
    measured_distances_u04,
    yerr=uncertainties_u04,
    fmt="none",
    capsize=4,
    ecolor="#4D4D4D",
    label="Uncertainty (±0.4 m)",
    zorder=2,
)
assert uncertainty_bars_u04.has_yerr

The yerr values specify vertical deviations from each point. Their statistical meaning does not come from the method name; that meaning must still be stated by the measurement procedure.

7.4.5 Stage 5 - labels with units and a legend

The data are now visible, but the graph still cannot be interpreted without units, ranges, and an encoding key.

Listing 7.6
ax_u04_lab.set_title("Measured distance and a constant-velocity model")
ax_u04_lab.set_xlabel("Time, t (s)")
ax_u04_lab.set_ylabel("Distance, d (m)")
ax_u04_lab.set_xlim(0, 6.25)
ax_u04_lab.set_ylim(0, 14)
ax_u04_lab.set_xticks(times_u04)
ax_u04_lab.set_yticks(range(0, 15, 2))
ax_u04_lab.grid(True, color="#D9D9D9", linewidth=0.8)
ax_u04_lab.set_axisbelow(True)
legend_u04 = ax_u04_lab.legend(loc="upper left", frameon=True)
fig_u04_lab.subplots_adjust(left=0.11, right=0.97, bottom=0.15, top=0.87)

assert "(s)" in ax_u04_lab.get_xlabel()
assert "(m)" in ax_u04_lab.get_ylabel()
assert len(legend_u04.get_texts()) == 3

7.4.6 Stage 6 - an accessible description and deterministic saving

A static figure needs a description that states the question, axes, markers, pattern, and limits of the conclusion. Quarto associates the following description with the lab figure. The same description is available in the ALT_TEXT constant and is written to a text artifact by the Unit 4 script.

fig_u04_lab
Graph of seven distance measurements against time. Blue circular points with uncertainty bars of 0.4 meters follow the orange dashed model line d equals 2t plus 1; all model values lie within the measurement intervals.
Figure 7.1: The completed construction after adding the model line, measurement points, uncertainty bars, labels with units, and a legend.

Calling savefig without fixing the size, metadata, font, and SVG hash salt can produce different bytes. The write_artifacts function fixes all of these, saves the data and description alongside the figure, and binds all three in a SHA-256 manifest. Close the lab canvas when finished so that a long-running process does not accumulate figures in memory.

Listing 7.7
plt.close(fig_u04_lab)

7.5 Axes, scales, and units

The label Time, t (s) states the variable name, symbol, and unit. The label Distance, d (m) does the same. Numbers without units are insufficient when comparing physical quantities.

The vertical axis in the Unit 4 figure begins at zero. This choice makes visual height proportional to the stated distance. Starting at zero is not an absolute rule for every line graph, but any truncation must be clearly visible and must not be used to exaggerate differences.

Suppose two bars represent 98 and 100. With a zero baseline, their height ratio is

1000980=100981.02. \frac{100-0}{98-0}=\frac{100}{98}\approx 1.02.

If the baseline is hidden at 97, the ratio becomes

100979897=3. \frac{100-97}{98-97}=3.

The difference in the data remains 2, but the second bar appears three times as tall as the first. This truncation may be useful for showing small changes, but the baseline and actual values must be stated clearly.

7.6 Aspect ratio and slope

The aspect ratio is the ratio of the plot region’s width to its height. A very narrow graph can make the same line look steep; a very wide graph can make it look flat. The model’s mathematical slope remains

m=ΔdΔt=2m/s, m=\frac{\Delta d}{\Delta t}=2\ \text{m/s},

regardless of the figure’s size on screen. Read the axis values and their units, rather than relying only on the angle in pixels. The unit script fixes the figure size and axis limits explicitly so that the result does not change with the window size.

7.7 Uncertainty is not decoration

In this example, the error bars represent measurement tolerance intervals [d0.4,d+0.4][d-0.4,d+0.4] meters. They are not automatically standard deviations, confidence intervals, or the ranges of all possible values. Those meanings are justified only if a measurement procedure or statistical model establishes them.

All model values lie inside the measurement intervals. A justified statement is, “These data are consistent with the model at the seven times examined.” An overstatement is, “The data prove that the model is always correct.” Other models may also fit the same seven intervals.

When comparing two series, use the same definition of uncertainty or explain the difference. Bars with different meanings must not be compared as though they were identical.

7.8 Text descriptions and accessibility

Useful alternative text does not merely say “line graph.” It includes:

  1. the graph’s purpose;
  2. the variables and their units;
  3. the encoding of markers and lines;
  4. the main pattern and important deviations; and
  5. the limits of the conclusion when the figure is easily misinterpreted.

The Unit 4 script embeds a title and description in the SVG metadata and writes the same description as unit04-alt.txt. The data remain available as CSV, so readers are not forced to obtain values from visual positions alone.

The legend, marker shapes, and line styles also repeat the information carried by color. This repetition is not wasteful: it makes the information accessible through more than one cue.

7.9 Deterministic artifacts

Run from the project root:

python source/code/unit04_visualization.py --output-dir output/unit04

The command produces four files:

  • unit04-data.csv: tabular data with units in the column names;
  • unit04-figure.svg: a vector figure with fixed scales and metadata;
  • unit04-alt.txt: the visual description in plain text; and
  • unit04-manifest.json: the question, axis policy, encoding meanings, claim limits, and SHA-256 hashes of the other three artifacts.

The script uses neither the network, the current time, nor random numbers. Data order, number formatting, font, figure size, SVG identifier salt, and metadata are fixed. In the same software environment, two runs must produce the same bytes.

Deterministic does not mean correct. An error written deterministically is still an error. Hashes help ensure that two people examine the same bytes; tests and reasoning check whether those bytes represent the intended meaning.

7.10 Visual evidence and mathematical proof

A figure can reveal patterns, anomalies, and possible counterexamples. It is very useful for framing the next question. However, seven points do not prove an equation for all times.

If motion is defined to have an initial position of 1 meter and a constant velocity of 2 meters per second, then the equation

d(t)=1+2t d(t)=1+2t

follows from the definition of constant-velocity motion. That is an argument about the model. The graph then checks whether the limited measurements are consistent with the model’s consequences.

Conversely, an observation whose interval does not contain the model value can provide evidence that the model, uncertainty, data, or implementation needs examination. It does not automatically identify which part is wrong.

7.11 Exercises

7.11.1 Exercise 1 - question and encoding

A friend plots the seven measurements but does not show the model line. Identify the missing information and one improvement that does not rely only on color.

Return to the main question: which two objects are being compared?

The graph lacks the reference values d=2t+1d=2t+1, so readers cannot directly assess whether the measurements are consistent with the model. Add a dashed model line with a legend entry or a direct label. A different line style keeps it distinguishable without relying on color.

7.11.2 Exercise 2 - a truncated axis

Two values are 98 and 100. Calculate their apparent height ratio for baselines of 0 and 97. Explain why the title “the second value is three times as large” is unjustified.

Apparent height is the value minus the baseline; the data magnitude remains the original value.

With a baseline of 0, the ratio is 100/981.02100/98\approx1.02. With a baseline of 97, the apparent height ratio is (10097)/(9897)=3(100-97)/(98-97)=3. However, the second value is only 100/98100/98 times the first, not three times. The number 3 describes the geometry of the truncated figure, not the data ratio.

7.11.3 Exercise 3 - the meaning of error bars

Are the ±0.4\pm0.4 meter bars in this unit 95% confidence intervals? Write a justified statement about these bars.

Use only the definition of uncertainty that was actually given.

There is no basis for calling them 95% confidence intervals. The unit defines them only as measurement tolerances from d0.4d-0.4 to d+0.4d+0.4 meters. A justified statement is: at all seven times, the model value lies within the stated tolerance interval.

7.11.4 Exercise 4 - an accessible description

Write two or three sentences of alternative text for the Unit 4 graph. Do not merely repeat the title.

Include the axes, encoding, pattern, uncertainty, and limits of the claim.

Example: “The graph compares measured distance in meters over times 0–6 seconds with the model d=2t+1d=2t+1. Circular points and ±0.4\pm0.4 meter bars represent measurements, while a dashed line represents the model; all model values lie inside the measurement intervals. The agreement of seven points supports consistency for these data, not proof that the model holds for all times.”

7.11.5 Exercise 5 - figures and proof

A graph shows 1,000 points exactly on the curve y=x2y=x^2. Does this prove that the program always calculates squares correctly? Give one computational step and one mathematical step that strengthen the check.

The figure contains only finitely many inputs and has limited visual resolution.

No. A thousand points check only the selected inputs, and small differences may be hidden by the figure’s resolution. Computational step: test boundary values, negative values, and deterministic random inputs by comparing the numerical outputs with exact results. Mathematical step: examine the algorithm’s definition and show that the operations applied to every input really produce xxx\cdot x on the stated domain.

7.11.6 Mastery exercise - from data to plot to manifest

Run write_artifacts in a new directory. Use code to demonstrate that this workflow produces CSV data, a static SVG figure, a text description, and a JSON manifest; that the manifest contains units and an accessible description; and that the hashes of the three artifacts recorded in the manifest match their actual bytes.

Use paths = write_artifacts(directory), read the JSON with json.loads, then calculate hashlib.sha256(paths[key].read_bytes()).hexdigest() for data, figure, and alt_text.

ImportantSelf-check

The following code must finish without an AssertionError. A temporary directory keeps the check from changing release artifacts.

import hashlib
import json
from pathlib import Path
import tempfile

from source.code.unit04_visualization import ALT_TEXT, write_artifacts

with tempfile.TemporaryDirectory() as tmp_u04:
    paths_u04 = write_artifacts(Path(tmp_u04))
    manifest_u04 = json.loads(paths_u04["manifest"].read_text(encoding="utf-8"))

    assert set(paths_u04) == {"data", "figure", "alt_text", "manifest"}
    assert manifest_u04["units"] == {
        "distance": "m",
        "time": "s",
        "uncertainty": "m",
    }
    assert manifest_u04["accessibility"]["description"] == ALT_TEXT
    for artifact_key_u04 in ("data", "figure", "alt_text"):
        digest_u04 = hashlib.sha256(
            paths_u04[artifact_key_u04].read_bytes()
        ).hexdigest()
        assert (
            manifest_u04["artifacts"][paths_u04[artifact_key_u04].name]
            == digest_u04
        )

Solution 7.1.

import hashlib
import json
from pathlib import Path
import tempfile

from source.code.unit04_visualization import ALT_TEXT, write_artifacts

with tempfile.TemporaryDirectory() as directory_u04:
    artifacts_u04 = write_artifacts(Path(directory_u04))
    manifest_u04 = json.loads(
        artifacts_u04["manifest"].read_text(encoding="utf-8")
    )

    assert artifacts_u04["data"].suffix == ".csv"
    assert artifacts_u04["figure"].suffix == ".svg"
    assert artifacts_u04["alt_text"].read_text(encoding="utf-8").strip() == ALT_TEXT
    assert manifest_u04["accessibility"]["svg_metadata"] is True
    assert manifest_u04["construction"][-1] == "deterministic_save"

    for artifact_name_u04 in ("data", "figure", "alt_text"):
        byte_u04 = artifacts_u04[artifact_name_u04].read_bytes()
        hash_u04 = hashlib.sha256(byte_u04).hexdigest()
        assert (
            manifest_u04["artifacts"][artifacts_u04[artifact_name_u04].name]
            == hash_u04
        )

CSV preserves values and units, SVG provides static output, alternative text offers a nonvisual route, and the manifest binds the bytes of all three. Matching hashes establish byte identity, not the correctness of the model.

7.12 Summary

  • Visualization begins with a question, not a choice of colors.
  • Positions, markers, lines, axes, scales, and units must have explicit meanings.
  • Truncated axes and aspect ratios can change the impression without changing the data.
  • The meaning of uncertainty must be stated; error bars do not automatically carry a statistical meaning.
  • Tabular data, alternative text, figures, and a hash manifest make artifacts checkable through more than one route.
  • Progressive construction using fig, ax, plot, scatter, and errorbar makes encoding, units, legends, and saving separately testable.
  • Graphs provide limited visual evidence and can reveal anomalies; general proof still requires an argument covering the entire domain.