data(gapminder_2007) + point + x(continent) + y(life)data(gapminder_2007) + point + x(col.continent) + y(col.life)data(gapminder_2007) + point + x(:continent) + y(:life)plot(data(gapminder_2007), point, x(col.continent), y(col.life))How does a question about your data turn into a specification? Reading was recognition; writing is recall. Psychologists use those two words for two kinds of remembering. Recognition starts from something in front of you, and asks only whether you have seen it before. That is what the last chapter did: it gave you finished sentences and asked what they say. Recall starts from nothing in front of you, and asks you to produce the thing itself. It is the harder of the two, and it is what writing a specification asks of you.
This chapter builds three sentences from three questions, the way you will actually work: start with the smallest legal sentence, look, refine. Then it introduces the other half of writing: what happens when a sentence is wrong, and why gog’s refusals are the fastest teacher in the book.
Start by looking at everything, the smallest sentence that shows the data:
data(gapminder_2007) + point + x(continent) + y(life)data(gapminder_2007) + point + x(col.continent) + y(col.life)data(gapminder_2007) + point + x(:continent) + y(:life)plot(data(gapminder_2007), point, x(col.continent), y(col.life))“Points, x is continent, y is life.” Informative, but the question asked for a comparison of typical values, not a cloud. Derive the summary, swap the mark’s derivation, keep everything else:
data(gapminder_2007) + bar * mean + x(continent) + y(life)data(gapminder_2007) + bar * mean + x(col.continent) + y(col.life)data(gapminder_2007) + bar * mean + x(:continent) + y(:life)plot(data(gapminder_2007), layer(bar, mean), x(col.continent),
y(col.life))Comparison wants order. order() sorts a categorical axis by a value, something no bare column can say:
data(gapminder_2007) + bar * mean + x(continent) + y(life) +
order(life, desc = TRUE)(data(gapminder_2007) + bar * mean + x(col.continent) + y(col.life) +
order(col.life, desc = True))data(gapminder_2007) + bar * mean + x(:continent) + y(:life) +
order(:life, desc = true)plot(data(gapminder_2007), layer(bar, mean), x(col.continent),
y(col.life), order(col.life, { desc: true }))Three steps, and each step added a word or swapped one: no rewriting, no second syntax. That is what compositionality buys you in practice.
The one-variable question first, then the split:
data(iris_flowers) + bar * bin + x(petal_length)data(iris_flowers) + bar * bin + x(col.petal_length)data(iris_flowers) + bar * bin + x(:petal_length)plot(data(iris_flowers), layer(bar, bin), x(col.petal_length))data(iris_flowers) + bar * bin + x(petal_length) + color(species)data(iris_flowers) + bar * bin + x(col.petal_length) + color(col.species)data(iris_flowers) + bar * bin + x(:petal_length) + color(:species)plot(data(iris_flowers), layer(bar, bin), x(col.petal_length),
color(col.species))And if the overlap bothers you, the outline form is one mark away; the staircase silhouette holds each bin’s height as an unfilled stroke, so the three species never paint over each other:
data(iris_flowers) + step * bin + x(petal_length) + color(species)data(iris_flowers) + step * bin + x(col.petal_length) + color(col.species)data(iris_flowers) + step * bin + x(:petal_length) + color(:species)plot(data(iris_flowers), layer(step, bin), x(col.petal_length),
color(col.species))The transform never changed. bin cut the same intervals all three times; only the geometry drawing them moved. A derivation you learn once works under every mark that can carry it.
The full sentence from the first chapter, written in one breath this time:
data(gapminder_2007) + point +
x(gdp, scale = "log") + y(life) +
color(continent) + size(population) +
title("The health and wealth of nations, 2007")(data(gapminder_2007) + point +
x(col.gdp, scale = "log") + y(col.life) +
color(col.continent) + size(col.population) +
title("The health and wealth of nations, 2007"))data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
color(:continent) + size(:population) +
title("The health and wealth of nations, 2007")plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life), color(col.continent), size(col.population),
title("The health and wealth of nations, 2007"))“Given gapminder 2007: points, x is gdp on a log scale, y is life, color by continent, size by population, titled.” Six atoms past the minimum, and still one sentence with no new grammar in it.
You will write wrong sentences. Everyone does. gog’s design promise is that a wrong sentence is refused with a direction, never absorbed into a silently wrong picture. Meet the refusals you are most likely to see this week.
Forgetting to bind the table. The sentence must start with the data:
gapminder_2007 + point + x(gdp) + y(life)Error:
! gog: a plot starts with `data()`, which names the table — columns are bare names and the nearest named table wins, so the name matters. Write `data(gapminder_2007) + point + ...`.
Quoting a column name, a habit from other packages. The refusal says which fix it wants:
data(gapminder_2007) + point + x("gdp") + y("life")Error:
! gog: `x("gdp")` is quoted, so it names a column called `gdp` — and there is no such column. Column names are written bare: `x(gdp)`. If you meant a fixed value rather than a column, that is `style()`.
gog: `y("life")` is quoted, so it names a column called `life` — and there is no such column. Column names are written bare: `y(life)`. If you meant a fixed value rather than a column, that is `style()`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
Mapping a channel to a column that cannot answer its question. size encodes magnitude, and a category has none:
data(gapminder_2007) + point + x(gdp) + y(life) + size(continent)Error:
! gog: `size(continent)` maps a categorical (text) column, but `size` on `point` needs a continuous (numeric) column. Use `color`, `shape`, or `pattern` to distinguish categories.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
Asking a transform to read a column you never named:
data(gapminder_2007) + bar * mean + x(continent)Error:
! gog: `bar` needs `y()` but none is set. Add `y(<column>)` — a bar cannot be drawn without it.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
Binning a category. bin cuts a continuous axis, and the refusal points at the atom that does count categories:
data(gapminder_2007) + bar * bin + x(continent)Error:
! gog: `bin` cuts a continuous axis into intervals, and `x(continent)` is categorical — a category is one slot, with no width to cut. To tally rows per category, `count` is the transform that does it: `bar * count`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
Each message names the rule and the way forward. Nothing rendered behind your back; nothing was dropped to make the picture come out. When you hit a refusal outside this list, read it the same way: it is the grammar telling you which law your sentence broke, and the fix is in the message.
Every refusal closes with a second offer, to set GOG_STRICT=0 and draw the plot anyway. That is an environment variable, and what it does to each kind of refusal (along with the one line that sets it in R, Python, Julia and JavaScript) is Drawing it anyway. For now, take the first offer: at this stage the refusals are the lesson.
If you started this part in one sitting, check yourself against its promise:
If that mostly works, the morning did its job. Part II gives every letter its full chapter; or jump straight to the cookbook and start answering your own questions, following the links back whenever a recipe uses a word you want more of.