| country | continent | year | life | population | gdp |
|---|---|---|---|---|---|
| Afghanistan | Asia | 2007 | 43.828 | 31889923 | 974.5803 |
| Albania | Europe | 2007 | 76.423 | 3600523 | 5937.0295 |
| Algeria | Africa | 2007 | 72.301 | 33333216 | 6223.3675 |
| Angola | Africa | 2007 | 42.731 | 12420476 | 4797.2313 |
| Argentina | Americas | 2007 | 75.320 | 40301927 | 12779.3796 |
4 Writing practice
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: you produce the answer yourself. It is the harder of the two, and it is what writing a specification asks of you.
This chapter works in the recall direction from here on. Three worked examples come first, showing the method you will use: start with the minimum sentence, look, refine. Five questions follow with nothing in front of you, each answered by a sentence you met in the last chapter: write yours before you scroll, then check it against the printed one. Last comes the other half of writing: what happens when a sentence is wrong, and how quickly a refusal teaches the rule.
4.1 Which continents live longest?
Before reading on, try to write the sentence that will answer it: which mark, which transform, which positions? Then build it the way this chapter always will. Start with the minimum sentence, the smallest one 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))“Given gapminder 2007: points, x is continent, y is life.” Informative, but the question asked for a comparison of typical values, and this shows every country. Swap the mark and give it a derivation; everything else stays:
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))“Given gapminder 2007: bars derived by mean, x is continent, y is life.”
A comparison reads best in order. A categorical column can carry a fixed order, declared where the data is made. The order wanted here depends on the means this plot just computed, so only the sentence can ask for it: order() sorts the axis by the column you name:
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 }))“Given gapminder 2007: bars derived by mean, x is continent, y is life, ordered by life, largest first.”
Three steps, and no step rewrote the sentence: words were added or swapped, never restructured. That is what composition gives you: each step keeps every word you already wrote.
4.2 How do petal lengths differ by species?
The last chapter drew both of these. Write the two sentences from memory: the one-variable question first, then the same question split by species.
| sepal_length | sepal_width | petal_length | species |
|---|---|---|---|
| 5.1 | 3.5 | 1.4 | setosa |
| 4.9 | 3.0 | 1.4 | setosa |
| 4.7 | 3.2 | 1.3 | setosa |
| 4.6 | 3.1 | 1.5 | setosa |
| 5.0 | 3.6 | 1.4 | setosa |
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))“Given the iris flowers: bars derived by bin, x is 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))“Given the iris flowers: bars derived by bin, x is petal length, color by species.”
If the overlapping bars are hard to compare, one mark changes that: step draws each bin’s height as an unfilled line, so the three species never cover 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))“Given the iris flowers: step outlines derived by bin, x is petal length, color by species.”
The transform never changed. bin cut the same intervals all three times; only the mark drawing them changed. A derivation you learn once works with every mark that can take it.
4.3 Where is the world’s wealth, and its health?
Income and life expectancy fill both positions. A reader also wants to know how many people each dot stands for. Chapter one built this sentence step by step. Write it in one pass now, adding size for population, then check:
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.” Three atoms added to the minimum sentence (color, size and title; the log scale is written inside x rather than counting as a word), and still no new grammar in it.
4.4 Five questions, nothing in front of you
Each question below matches one plot from the last chapter. Write your sentence first; the printed one is there to check against, not to read first.
Which countries won the most gold? Every country name has to stay readable, so lay the bars down.
| country | gold | silver | bronze |
|---|---|---|---|
| USA | 46 | 37 | 38 |
| China | 38 | 31 | 22 |
| Great Britain | 29 | 17 | 19 |
| Russia | 19 | 18 | 9 |
| Germany | 17 | 10 | 15 |
data(medals) + bar + x(gold) + y(country)data(medals) + bar + x(col.gold) + y(col.country)data(medals) + bar + x(:gold) + y(:country)plot(data(medals), bar, x(col.gold), y(col.country))“Given the medals: bars, x is gold, y is country.” One swap of the two bindings lays the bars down, and no new word was needed.
How is life expectancy distributed, drawn as a smooth curve rather than counted into bins?
data(gapminder_2007) + line * density + x(life)data(gapminder_2007) + line * density + x(col.life)data(gapminder_2007) + line * density + x(:life)plot(data(gapminder_2007), layer(line, density), x(col.life))“Given gapminder 2007: a line derived by density, x is life.” The histogram’s sibling: one atom swapped, same question.
How did life expectancy move across the decades in the five Asian countries, one line per country?
| country | continent | year | life | population | gdp |
|---|---|---|---|---|---|
| China | Asia | 1952 | 44.00000 | 556263527 | 400.4486 |
| China | Asia | 1957 | 50.54896 | 637408000 | 575.9870 |
| China | Asia | 1962 | 44.50136 | 665770000 | 487.6740 |
| China | Asia | 1967 | 58.38112 | 754550000 | 612.7057 |
| China | Asia | 1972 | 63.11888 | 862030000 | 676.9001 |
data(gapminder_asia) + line + x(year) + y(life) + color(country)data(gapminder_asia) + line + x(col.year) + y(col.life) + color(col.country)data(gapminder_asia) + line + x(:year) + y(:life) + color(:country)plot(data(gapminder_asia), line, x(col.year), y(col.life),
color(col.country))“Given gapminder Asia: lines, x is year, y is life, color by country.” color splits first and colors second, exactly as it did in the last chapter.
Does the wealth and health relationship hold inside each continent, one panel per continent?
data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) | facet(continent)data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) | facet(col.continent)data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) |
facet(:continent)plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life), across(col.continent))“Given gapminder 2007: points, x is gdp on a log scale, y is life, split into panel columns by continent.” The | and its facet, with scales shared across panels.
Do the three iris measurements separate the species when seen together?
data(iris_flowers) + point +
x(sepal_length) + y(sepal_width) + z(petal_length) +
color(species)(data(iris_flowers) + point +
x(col.sepal_length) + y(col.sepal_width) + z(col.petal_length) +
color(col.species))data(iris_flowers) + point + x(:sepal_length) + y(:sepal_width) +
z(:petal_length) + color(:species)plot(data(iris_flowers), point, x(col.sepal_length), y(col.sepal_width),
z(col.petal_length), color(col.species))“Given the iris flowers: points, x is sepal length, y is sepal width, z is petal length, color by species.” The cube again, and you wrote it yourself this time.
4.5 When the engine says no
You will write wrong sentences. Everyone does. gog’s design promise is that a wrong sentence is refused with a direction, never drawn with the wrong part quietly left out. Here are the refusals you are most likely to see first.
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 + ...`.
Each binding words that advice for its own spelling of a column, so this one refusal is shown in R alone.
Quoting a column name, a habit from other packages. The refusal names the fix:
data(gapminder_2007) + point + x("gdp") + y("life")data(gapminder_2007) + point + x("gdp") + y("life")data(gapminder_2007) + point + x("gdp") + y("life")plot(data(gapminder_2007), point, x("gdp"), y("life"))Error:
! gog: `x("gdp")` binds a *value*, and a channel takes a *column*: `x(gdp)` maps the column called `gdp`.
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)data(gapminder_2007) + point + x(col.gdp) + y(col.life) + size(col.continent)data(gapminder_2007) + point + x(:gdp) + y(:life) + size(:continent)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
size(col.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.
Forgetting the column a summary reads. The refusal names the missing y first, because the bar cannot stand without it:
data(gapminder_2007) + bar * mean + x(continent)data(gapminder_2007) + bar * mean + x(col.continent)data(gapminder_2007) + bar * mean + x(:continent)plot(data(gapminder_2007), layer(bar, mean), x(col.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)data(gapminder_2007) + bar * bin + x(col.continent)data(gapminder_2007) + bar * bin + x(:continent)plot(data(gapminder_2007), layer(bar, bin), x(col.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 fix. Nothing was drawn without telling you, and no binding was dropped to complete the plot. Read any refusal outside this list the same way: it names the law the sentence broke, and it names the fix.
The last three refusals close with a second offer: set GOG_STRICT=0 and draw the plot anyway. The first two never reach the engine, so there is nothing to draw. GOG_STRICT is an environment variable, and Drawing it anyway shows what it does to each kind of refusal, with the one line that sets it in each language. For now, make the fix the message asks for: at this stage the refusals are the lesson.
4.6 The morning checkpoint
If you started this part in one sitting, check yourself against its promise:
- Read any sentence in this book aloud before looking at its plot.
- Given a question (distribution? comparison? relationship? change over time?), name the mark and transform that answer it.
- Predict whether a sentence will render or refuse, and roughly what the refusal will say.
If that mostly works, the morning worked. Part II gives every letter its full chapter. Or go straight to the cookbook and answer your own questions, following a link back whenever a recipe uses a word you want explained.