Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Courses involving computer programming frequently start with control structures and data structures. Extensive education research has underscored the value in starting with visualization instead. Visualization offers β€˜early wins’, producing something compelling and often fun. Like many of our skills, we will continue to refine and extend our knowledge of data visualization throughout the course. Mastery comes through repetition.

The Grammar of GraphicsΒΆ

We use plotnine, which implements the Grammar of Graphics in python. This approach provides a powerful abstraction around data visualization concepts. The theory has been around for a long time, but became mainstream in data science through the work of Hadley Wickham and the ggplot2 implementation in R. Python libraries have adopted the idea more recently, and it can now be found in plotnine, altair, and seaborn’s object interface.

Mapping data to aestheticsΒΆ

The fundamental principle of the grammar of graphics is that it expresses mappings from data to aesthetics of a graph: color, size, x and y position, and so on.

import ibis
from plotnine import ggplot, aes, geom_line, labs

con = ibis.duckdb.connect()
co2 = con.read_csv("data/co2.csv")
co2.head().to_pandas()

This dataset is in β€œlong” form: a decimal_date column, a name column identifying which series a row belongs to, and a value column. The categorical values in name can be mapped to color:

(
    ggplot(co2.to_pandas(), aes(x="decimal_date", y="value", color="name"))
    + geom_line()
    + labs(x="Year", y="CO2 (ppm)")
)

Read that as three statements: the data and which columns map to which aesthetics (aes), the geometry used to draw them (geom_line), and the labels. Layers combine with +.

Contrast with asserting appearance directlyΒΆ

Older plotting engines such as matplotlib, and the original seaborn function interface, do not reflect these principles. The equivalent there looks like this:

# the older style, which we avoid
import seaborn as sns
wide = co2.pivot_wider(names_from="name", values_from="value").to_pandas()
sns.lineplot(wide, x="decimal_date", y="average", color="blue")
sns.lineplot(wide, x="decimal_date", y="deseasonalized", color="red")

Here x and y still refer to columns, but color is asserted manually. The relationship between color and data structure is not captured. To change the color scheme you must update each assertion by hand. This is harder to generalize and more error-prone.

Note also that while this is slightly more concise for a single line, it becomes rapidly more verbose as series are added, repeating parts that did not need repeating. Add a third series and you add a third near-identical call. In the grammar version, a third series requires no code change at all β€” it is already in the data.

Tidy dataΒΆ

That last point is worth dwelling on. The grammar version works because the data is tidy: each column is a variable, each row is an observation. The series identity lives in a column, so the grammar can map it to an aesthetic.

This is sometimes called β€˜long’ form, and more formally Cobb’s Third Normal Form. It takes getting used to, and frequently requires some transformation to achieve, since many wild-caught datasets do not follow the practice β€” especially older and smaller ones. pivot_longer() and pivot_wider() are how you move between shapes.

Notice that the grammar and tidy data reinforce each other. A well-designed plotting abstraction pushes you toward a well-organized data structure, and a well-organized data structure makes the plot a one-liner. That is what a good abstraction does: it makes the right thing easy and the wrong thing awkward.

Defaults are an argument tooΒΆ

The grammar version also gets a default color palette informed by visualization research, rather than whatever colors you happened to name. "blue" and "red" are choices you made without evidence. Good defaults are a real feature β€” you should override them deliberately, not by accident.

ReferenceΒΆ