| 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 |
2 The grammar
What do you have to say about your data before a plot can be drawn? gog’s whole vocabulary is a short list of ordinary English words. Learn them once and you can read, and write, any plot. This book calls each word an atom, a piece that nothing divides further.
This chapter is a small dictionary, and a dictionary is not read the way a story is. Nobody memorizes one before writing, and these lists ask for no memorizing either. Draw your own data with the few words your question needs, and the plot shows you what each one does. Each plot you write teaches you a few more words, and you can use them in the next one.
2.1 The kernel
Here is the whole vocabulary: eleven kinds of word, and every word in each kind.
| Kind | The words |
|---|---|
| Sources | data query |
| Marks | point line area bar step interval box ribbon text path rule zone surface edge |
| Channels | x y z color size shape pattern opacity group label play |
| Transforms | bin smooth straight count density proportion sum mean median max min quantile cumulative range confidence deviation smooth_band straight_band bounds partition flow layout cluster, and the four collision modifiers, dodge stack jitter repel |
| Operators | + layers, * derives, | and / arrange |
| Scales | linear log time category order |
| Spaces | flat space polar nest map globe network |
| Settings | style theme palette |
| Labels | title subtitle caption x_label y_label z_label |
| Facets | facet |
| Selections | brush |
Every word in it is a common English word, with no abbreviations and no acronyms. A common word often names something else already. Several of these name something in R, Python or Julia before you load gog (with library(gog) in R; A first plot gives the command for each language). What happens then is the subject of Names the host language already uses, near the end of this chapter.
Read the kernel as two groups: what a plot needs, and what refines or arranges it. Five kinds build a plot. A source names the table that supplies the rows and the columns. A mark is the shape drawn for each row. A channel maps a column to something you can see, such as a position or a color. A transform derives new values from the columns you named. An operator joins the words into one sentence.
The other six refine a plot or arrange it, and none of them is required. A scale decides how a number becomes a position. A space is the surface the plot is drawn on: a flat page, a cube, a circle, a map, a network. A setting changes how the plot looks without saying anything about the data, and a label names it. A facet cuts one plot into panels. A selection picks rows out of a plot that is already drawn.
So the minimum sentence is a source, a mark, and the position channels that mark needs:
data(table) + mark + x(column) + y(column)
Two things are true of every word in the kernel. The engine implements every word in it, though it does not draw every pairing of words. And gog never accepts a word and then quietly ignores it: a sentence it cannot draw is refused, and the refusal names the rule. See Design laws for the difference between illegal (the grammar forbids it) and unsupported (the grammar allows it; the engine does not draw it).
2.2 The colors of the code
In every sentence in this book, each word of the kernel is printed in the color of its kind. These colors belong to the code, never to a plot. They are not decoration: they show the two groups above.
The colors are this book’s own. gog does not add them to your editor: RStudio, VS Code and other editors color gog code the way they color any other R, Python, Julia or JavaScript, so you will not see these colors there.
Four of the five kinds that build a plot are words, and each takes a color of its own. The fifth is the operator. In R, Python and Julia an operator is a symbol such as +, which is easy to see without a color, so it takes none. JavaScript spells the operators as words, and they take no color either. The six kinds that refine or arrange a plot share one muted color, because none of them is required.
Your own names keep the color of the text around them. That is the first thing the coloring tells you: which words belong to gog, and which are yours.
data(gapminder_2007) + bar * bin + x(life) + style(color = "tomato")data(gapminder_2007) + bar * bin + x(col.life) + style(color = "tomato")data(gapminder_2007) + bar * bin + x(:life) + style(color = "tomato")plot(data(gapminder_2007), layer(bar, bin), x(col.life),
style({ color: "tomato" }))“Given gapminder 2007: bars derived by bin, x is life, colored tomato.”
data names the table, bar is the mark, bin is the transform, and x is the channel. Those four build the plot. style refines it, so it takes the muted color, and gapminder_2007, life and "tomato" are yours.
Inside style, the word color is printed like your own names. Here it names a property rather than a column, so it is not the color channel: an argument name is never one of gog’s words.
An ordinary syntax highlighter could not have drawn these colors. It colors code by the host language’s own categories, and the four languages disagree about gog’s words. R sees data as a function, Python sees a plain name, and none of the four has a category for a mark. So the four spellings of one sentence would be colored four different ways. Coloring by kind instead gives a word the same color in all four languages. Your own editor highlights the ordinary way, so do not expect these colors there: on your screen a gog sentence looks like ordinary code.
Each kind has a section below, in the order of the kernel above: the five that build a plot first, then the six that refine or arrange one. Each section gives the words and what they do, and names the chapter that teaches them in full.
2.3 Sources: where the rows come from
Every sentence begins with a table, and a source is the word that names one. There are two.
| Source | Where the rows come from |
|---|---|
data |
A table already in memory |
query |
A table a database returns, named by SQL |
query() takes the place of data() in the sentence, and everything after it is unchanged:
query(connection, "SELECT ...") + mark + x(column) + y(column)
The SQL stays inside query(). The columns after it are still bare names, because a database returns a table, and every sentence begins with a table. See Data.
2.4 Marks: the shape drawn for each row
A mark is the geometric form drawn for each row of data. There are fourteen, and you do not need to learn them all now. A few of them draw most of the plots in this book. Each mark has a chapter of its own in Part II, where its plots show what a one-line description cannot.
| Mark | What it draws |
|---|---|
point |
One glyph (a small drawn symbol, a dot by default) per row |
line |
Rows connected in x order: one value per x, read left to right |
bar |
Rectangle from baseline to value, vertical or horizontal |
area |
Filled region between a line and the baseline |
step |
A line that holds each value until it changes (cumulative curves, histogram outlines) |
interval |
A whisker spanning low→high at each x: error bars, ranges; needs a pair transform (one that gives a low and a high), such as range |
box |
Distribution per group: box, median, whiskers to 1.5 times the interquartile range, outliers as dots; carries its own summary |
ribbon |
A filled band from a low boundary to a high one across x: confidence / spread bands; needs a pair transform, such as range |
text |
A string at each position: its content comes from the label channel |
path |
Rows connected in the table’s order: a route that may return the way it came; takes style(arrow = ) |
rule |
A line at one position, spanning the other axis: reference lines, and rugs via style(reach = "edge") |
zone |
A shaded region: bounded where its sides are known, spanning the whole panel where they are not. Five things can decide the sides: a categorical position (each category gets its own slot: the tile plot), bounds (names them), bin (cuts them: the heatmap), density (cuts or traces them), and a boundary (the data draws them: the choropleth, in map()) |
surface |
A sheet above the x-y plane: one row per (x, y) crossing, with a face between each block of four, or one row per cut cell, drawn flat at its own height. The only mark that must be drawn in space(), the 3-D cube |
edge |
A stroke between two named things: one row per relation of a network. Its endpoints come from layout, and it draws in network() |
2.5 Channels: a column made visible
The first chapter added one word, color(continent), and a legend appeared beside the plot. That word is a channel. A channel maps a data column to a visual property of the mark. Each row’s value sets that property for the row’s own mark. facet also takes a column, but it belongs to the whole plot, while a channel belongs to the layer it is written for (faceting comes later in this chapter). play is the channel closest to facet: it splits the rows into frames the way a facet splits them into panels, but it can move one layer while another does not move. So adding a channel to the sentence adds one thing the reader can see:
data(gapminder_2007) + point + x(gdp) + y(life) + color(continent)data(gapminder_2007) + point + x(col.gdp) + y(col.life) + color(col.continent)data(gapminder_2007) + point + x(:gdp) + y(:life) + color(:continent)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
color(col.continent))“Given gapminder 2007: points, x is gdp, y is life, color by continent.”
Channels do not all do the same work. What separates them is the guide each one gets, the part of the plot that lets a reader decode it. An axis is a guide, and it is more than a line: it includes the tick marks, the values written at the ticks, and the axis name. A legend is the guide for a channel such as color:
| Channel | What it does | Guide |
|---|---|---|
x |
Horizontal position, from a continuous or categorical column | an axis |
y |
Vertical position, from a continuous or categorical column | an axis |
z |
Depth, the third position: it makes a plot 3-D. Drawn by point, bar, interval, box, text, path and surface |
an axis |
color |
Fill or stroke color, from a continuous or categorical column | a legend |
size |
Radius on point, font size on text |
a legend |
shape |
Glyph, one of seven (circle, square, triangle, diamond, cross, star, and wye, a Y shape), on point |
a legend |
pattern |
Texture: a fill’s hatch, a stroke’s dash. Every mark but point, text and surface |
a legend |
opacity |
How opaque each mark is, on point, bar, text, edge and surface |
a legend |
group |
Splits a mark into one piece per category, without encoding it. On line, area, step, interval, box, ribbon, path, zone and surface, and on point, bar and text when the layer has a statistic or dodge, and on bar also when it has stack |
nothing |
label |
The string a text mark draws, its content |
nothing |
play |
Cuts the plot into frames and plays them in sequence, from a continuous or categorical column | a strip |
The Guide column sorts the channels into four groups, and a Korean syllable shows how they fit together. It has three slots: an initial consonant, then a vowel, then an optional final consonant. The mark is the initial consonant, so the channels are the other two.
x, y and z are the vowels: a mark with no position has no place to be drawn, so every plot needs one. Usually you write it. A transform can also compute it: in a pie, count gives each slice its size, and in a network, layout places every node. A position you write usually gets an axis for reading its values.
The five that get a legend, color, size, shape, pattern and opacity, are the final consonants, the optional ㅁ (m) that turns 가 (ga) into 감 (gam). None is ever required, and each changes what the plot shows, so each needs a legend for the reader to read its values.
group and label are neither. group splits without encoding: on a line it performs the very same split color does, one line per category, and then stops. group is color with the visible differences and the legend removed. So it separates the rows into pieces but shows nothing about them. label supplies a text mark’s content rather than refining it.
play is the fourth kind, and like faceting it is outside the syllable. Neither changes how one picture looks: faceting splits the plot into panels, and play cuts it into frames and plays them in order. Its guide is a strip naming the frame on screen. See Play.
Not every channel applies to every mark. A line is drawn with one stroke, so it takes no per-row size or opacity. gog has a rule like this for every pair of mark and channel: see Design laws, and Combinations for the whole mark-by-channel grid.
2.6 Transforms: values the data does not hold
A transform derives new values from the columns you named, and attaches to a mark with *. The mark stays what it is: bar * bin still draws bars, one for each band that bin counts.
Twelve of them replace values with a computed statistic. The five summaries sum to min share one row.
| Transform | What it computes |
|---|---|
bin |
Cuts a continuous column into bands and counts the rows in each |
count |
Tallies the rows in each category |
proportion |
The same tally, as a share of the whole |
density |
A smooth curve of where the rows lie |
smooth |
A trend line fitted through the points |
straight |
The straight line fitted through the points by least squares |
sum mean median max min |
Reduce a column to one value per group |
quantile |
Reduce a column to the value that a given share of the rows fall below, quantile(0.9) |
One more transform is not a statistic of a group. cumulative keeps every position and replaces each value with the total so far, so it depends on the order along the axis. It usually follows another transform, as in bar * count * cumulative, which counts first and then adds the counts up:
| Transform | What it computes |
|---|---|
cumulative |
Adds each position’s value to the ones before it: the running total |
Six write a low and a high together, which is what a span needs. These are the pair transforms:
| Transform | What it computes |
|---|---|
range |
A band between two quantiles. It spans the whole group unless you name the two |
confidence |
The interval around the mean, which shows how precisely the mean is known |
deviation |
A band of standard deviations around the mean, which shows how far the rows lie from it |
smooth_band |
The band around smooth’s curve, which shows how precisely the trend is known at each x |
straight_band |
The band around straight’s line, which shows how precisely the line is known at each x |
bounds |
A low/high pair the table already holds, in two named columns |
Four are the collision modifiers: they compute nothing, and they move marks that would otherwise be drawn in the same place:
| Transform | What it does |
|---|---|
dodge |
Puts them side by side |
stack |
Piles them on top of one another |
jitter |
Spreads them sideways along a category, never along the measured axis |
repel |
Moves labels off one another when they overlap |
And four transforms compute an entire picture:
| Transform | What it computes |
|---|---|
partition |
Divides a whole among nested parts: the treemap and the mosaic |
flow |
Carries a magnitude through its stages: the flow diagram |
layout |
Places a network’s nodes from an edge table: the network diagram |
cluster |
Joins the closest categories of an axis one pair at a time: the cluster tree. It also puts a tile plot’s slots in a useful order |
See Transforms for each one in detail, and Combinations for which marks take which.
2.7 The + operator: layering
A plot often needs more than one mark on one pair of axes, and + is what joins them. It is the same + that joins every other word in a sentence.
# One layer
data(gapminder_2007) + point + x(gdp) + y(life)data(gapminder_2007) + point + x(col.gdp) + y(col.life)data(gapminder_2007) + point + x(:gdp) + y(:life)plot(data(gapminder_2007), point, x(col.gdp), y(col.life))“Given gapminder 2007: points, x is gdp, y is life.” One mark, one layer.
Bars can show the amounts, with a point on top of each bar to mark the exact value:
| year | sales |
|---|---|
| 2019 | 120 |
| 2020 | 135 |
| 2021 | 128 |
| 2022 | 152 |
| 2023 | 168 |
# Two layers: bars behind, points on top
data(actuals) + x(year) + y(sales) +
bar + style(color = "lightsteelblue") +
point + style(color = "steelblue", size = 6)(data(actuals) + x(col.year) + y(col.sales) +
bar + style(color = "lightsteelblue") +
point + style(color = "steelblue", size = 6))data(actuals) + x(:year) + y(:sales) + bar +
style(color = "lightsteelblue") + point +
style(color = "steelblue", size = 6)plot(data(actuals), x(col.year), y(col.sales), bar,
style({ color: "lightsteelblue" }), point,
style({ color: "steelblue", size: 6 }))“Given the actuals: x is year, y is sales, bars and also points.”
Each mark opens a new layer. The layers are drawn in the order you wrote them, so the first mark is at the bottom and every later mark is drawn over it.
2.7.1 Which marks a channel reaches
Once a sentence has two marks, you need to know which mark each channel changes. A channel written before any mark reaches every mark. In the bars-and-points sentence above, x(year) and y(sales) come first, so both marks use them. A channel such as color, written after a mark, belongs only to the mark just before it. Positions are shared in any case, because every mark uses the same axes, so moving x and y past a mark changes nothing.
The rule matters most for a line, because color splits a line into one line per category. gapminder_asia has one row for each of five Asian countries in each of twelve years, 1952 to 2007:
| 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 |
Here color(country) comes before both marks, so the lines and the points both use it, and each country gets its own line:
data(gapminder_asia) + x(year) + y(life) + color(country) + line + pointdata(gapminder_asia) + x(col.year) + y(col.life) + color(col.country) + line + pointdata(gapminder_asia) + x(:year) + y(:life) + color(:country) + line +
pointplot(data(gapminder_asia), x(col.year), y(col.life), color(col.country),
line, point)“Given gapminder Asia: x is year, y is life, color by country, lines and also points.”
Written last, just after point, the same color(country) belongs to the points alone. Nothing splits the line, so one line joins the points of all five countries. A line joins its rows in x order, and here five rows share each year, so the line zigzags up and down at every year:
data(gapminder_asia) + x(year) + y(life) + line + point + color(country)data(gapminder_asia) + x(col.year) + y(col.life) + line + point + color(col.country)data(gapminder_asia) + x(:year) + y(:life) + line + point +
color(:country)plot(data(gapminder_asia), x(col.year), y(col.life), line, point,
color(col.country))gog: up to 5 rows of `line` share one value of `x(year)`, and nothing splits them, so they are joined in x order and the stroke zigzags inside that value. If you have several series, add `color(<field>)` or `group(<field>)`; for one value per `x`, summarize them with `line * mean`.
“Given gapminder Asia: x is year, y is life, a line and also points colored by country.”
gog prints a warning about the zigzag and suggests color(<field>) or group(<field>), although the sentence already has color(country). That color belongs to the points, so nothing splits the line.
The sentence read aloud under each plot also says which marks a channel reaches. “Color by country” names no mark, so it reaches every mark. “Points colored by country” reaches the points only.
With one mark, it makes no difference where you write a channel: data(gapminder_2007) + point + x(gdp) + y(life) and data(gapminder_2007) + x(gdp) + y(life) + point draw the same plot. This book writes the mark first, because the mark is what the sentence is about (The order of +). With several marks, write first what every mark shares. Then write each mark, followed by what belongs to that mark alone.
Order appears more than once in this grammar, and it does not mean the same thing every time. Which orders matter gathers all of it in one place: what you may reorder freely, what the engine refuses, and the two orders that change the picture with no warning. Encoding scope gives the rule for every kind of word.
2.8 The * operator: combining
A histogram is not in your table: the bands and their counts have to be computed before anything is drawn. * is how a sentence asks for that.
* attaches a transform to a mark. The mark is always on the left; the transform is on the right. Where two transforms follow one mark, either order draws the same plot. Write them in the order they happen: bar * bin * mean cuts the axis into bands, then averages inside each one.
bar * bin # histogram
line * smooth # smoothed trend line
zone * bin # heatmap: the same `bin`, cutting both axesThe last line, zone * bin, shows the pattern: one transform means one thing, and the mark decides where it applies. In a histogram, each bar’s height shows the count, so one axis is used for the count and bin cuts only the other. A zone shows its count by color instead, so no axis is used for the count, and both axes are free for columns. In zone * bin + x(gdp) + y(life), bin cuts gdp into bands along the x axis and life into bands along the y axis. Each cell is where a gdp band meets a life band, and its color shows how many rows fall in that cell. Change the mark to bar and add space(), the 3-D cube, and the count becomes the height of a bar instead of a color. The 3-D histogram compares the two. When one of a zone’s positions is categorical, bin cuts only the other one, because a category is already divided into slots. The zone chapter’s mixed mesh draws one: life cut into bands, one row of cells per continent.
The six summaries (sum, mean, median, max, min, quantile) differ from bin in one way: they take the measurement from a column you name, instead of counting rows. One channel carries the measurement: y on a bar, color on a zone, z in space(). A summary groups the rows by every position that does not carry the measurement, then reduces that channel’s column to one value inside each group. So zone * mean + x(a) + y(b) + color(v) averages v inside every cell two categories cross. Transforms draws all six from one column, so you can compare their answers.
See Operators for the full rules, and Transforms for each transform in detail.
2.9 The | and / operators: faceting, or composing
Across the world, life expectancy rises with income. One scatter of 142 countries cannot show whether that holds inside each continent. A second question, how many countries each continent has, needs a plot of its own beside the first. Two operators put plots or panels in one figure: | puts them side by side, and / puts one above the other. Whether those are panels or whole plots is decided by what you write on the right.
With facet() on the right they split one plot into panels, one per category of the column it names:
# One panel per continent, side by side
data(gapminder_2007) + point + x(gdp) + y(life) | facet(continent)data(gapminder_2007) + point + x(col.gdp) + y(col.life) | facet(col.continent)data(gapminder_2007) + point + x(:gdp) + y(:life) | facet(:continent)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
across(col.continent))“Given gapminder 2007: points, x is gdp, y is life, split into panel columns by continent.”
Every panel shares one scale, which is what makes the panels comparable. Write free = TRUE inside a position, as in y(population, free = TRUE), and that axis is fitted to each panel’s own data instead. Use both operators together and the panels are laid out in rows and columns. See Faceting for the full rules.
With another plot on the right they make an arrangement: the two plots side by side or one above the other, each keeping its own coordinate space:
# Two plots, side by side
(data(gapminder_2007) + point + x(gdp) + y(life)) |
(data(gapminder_2007) + bar * count + x(continent))((data(gapminder_2007) + point + x(col.gdp) + y(col.life)) |
(data(gapminder_2007) + bar * count + x(col.continent)))(data(gapminder_2007) + point + x(:gdp) + y(:life)) |
(data(gapminder_2007) + bar * count + x(:continent))beside(plot(data(gapminder_2007), point, x(col.gdp), y(col.life)),
plot(data(gapminder_2007), layer(bar, count), x(col.continent)))“Given gapminder 2007: points, x is gdp, y is life, beside bars derived by count, x is continent.”
The parentheses keep each plot whole until | joins the two. Each language groups its operators in a fixed order, called precedence. Arithmetic does the same: it groups * before + and reads 2 + 3 * 4 as 2 + (3 * 4). In R and Python, | groups after +, so gog would draw this sentence without the parentheses. / groups before +, so without the parentheses gog would refuse the same two plots joined by /. In Julia, gog refuses both. So whenever this book composes plots, each plot goes inside its own parentheses. Parentheses in an arrangement shows what each language’s order does to an arrangement.
Composed plots share nothing by default. The arrangement decides which axis is aligned: x when one plot is above the other, y when they are side by side. If both plots name the same column on that axis and read it the same way, with the same scale and limits, they share it: one scale, one range of values, drawn once. Because of that rule, a marginal plot (a scatter with a small distribution drawn along each edge) needs no chart type of its own: it is several plots joined by | and /. See Composition.
2.10 Scales: how a number becomes a position
Incomes in gapminder_2007 run from under 300 dollars to nearly 50,000. Most countries therefore crowd the left end of a linear axis, and a log scale spreads them out. A scale is written on the binding, the channel with its column in it (A first plot says why the same act is also called mapping). It is not a word of its own, because a scale belongs to one channel and means nothing without it:
data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
x_label("GDP per person")(data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
x_label("GDP per person"))data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
x_label("GDP per person")plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life), x_label("GDP per person"))“Given gapminder 2007: points, x is gdp on a log scale, y is life.”
x_label() writes your own words under the axis in place of the column name. It is a label, not a scale, and Labels, below, covers all six.
| Scale | What it does |
|---|---|
linear |
Equal steps for equal differences. The default |
log |
Equal steps for equal ratios, on x, y, color, size and opacity, with any base |
time |
Positions dates and times, chosen from the column type: dates and date-times |
category |
One slot per distinct value, chosen from the column type: columns of text, and columns with a declared order |
order |
The order of the categories along an axis. You can also declare the order on the column: a factor in R, ordered() in the other three |
A scale is not a transform: it produces no new values, only different positions for the ones you have. Scales explains what happens when you use both, such as a log scale on bar * bin or bar * sum.
The same binding takes two more arguments, limits and tick_count. limits gives the range of values a channel covers, when you want a range other than the one in the data. tick_count says how many ticks an axis should have. Both describe the scale, so both are written where the scale is written. Three more say how the numbers on an axis or a legend are written: percent, short and decimals. Scales has all of them.
2.11 Spaces: the surface a plot is drawn on
A space decides what a position means. Every plot so far has been drawn on the plane. That space is flat, and you never have to write it.
| Space | Where the marks are drawn |
|---|---|
flat |
The plane: x across, y up. The default |
space |
The cube: x, y and z, projected onto the page |
polar |
The plane bent into a circle: one position becomes an angle, the other a radius |
nest |
Rectangles inside rectangles, each sized by its share of the one holding it |
map |
The earth flattened: longitude across, latitude up |
globe |
The earth as a sphere, seen from one side: marks are drawn only on the half that faces you |
network |
The space of a network: positions come from layout and their numbers mean nothing, so it draws no axes. State a viewing angle in network() and the layout is computed in the cube instead |
A plot is drawn in one space, and every layer of it uses that space. See Space, Polar, Nest, Map, Globe and Network.
2.12 Settings: values you set rather than map
Every channel above maps a column. color(continent) gives each continent a color, and a legend tells the reader which color is which.
Making every point one color maps no column. It needs no scale and no legend, so it is not a channel. That is style:
| Setting | What it fixes |
|---|---|
style(color = ) |
One color for the whole layer |
style(opacity = ) |
One opacity, 0–1 |
style(size = ) |
Point radius, line stroke width, or a label’s font size |
style(shape = ) |
One glyph for every point |
style(pattern = ) |
The texture of a mark’s paint: a stroke’s dash (solid/dashed/dotted/dotdash/longdash), a fill’s hatch (hatch/crosshatch/stripes/grid/dots). Every mark but point, text and surface |
palette( ) |
Which colors the color channel uses |
theme( ) |
The page around the plot: grid, background, fonts, size |
# map: color carries the continent, and a legend decodes it
data(gapminder_2007) + point + x(gdp) + y(life) + color(continent)data(gapminder_2007) + point + x(col.gdp) + y(col.life) + color(col.continent)data(gapminder_2007) + point + x(:gdp) + y(:life) + color(:continent)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
color(col.continent))Now the same points with one color and one opacity set for all of them:
# set: one color, no column mapped, no legend drawn
data(gapminder_2007) + point + x(gdp) + y(life) +
style(color = "tomato", opacity = 0.5)(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
style(color = "tomato", opacity = 0.5))data(gapminder_2007) + point + x(:gdp) + y(:life) +
style(color = "tomato", opacity = 0.5)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
style({ color: "tomato", opacity: 0.5 }))“Given gapminder 2007: points, x is gdp, y is life, colored tomato, with opacity 0.5.”
See Setting vs mapping for the full rules.
2.13 Labels: the words on the plot
An axis carries the name of the column bound to it. A label replaces that name when the column name is not what a reader should see.
| Label | What it names |
|---|---|
title |
The plot |
subtitle |
What the plot shows, in a smaller line under the title |
caption |
Where the numbers came from, in a line at the bottom |
x_label y_label z_label |
One axis each |
title(), subtitle() and caption() add words of their own rather than replacing a column’s name. Without them, a plot cannot say what it is about or where its numbers came from. The title is a bold line above the plot. The subtitle is a smaller, lighter line under it, for what the plot shows, such as which countries and which year. The caption is in the same small size as the tick labels, at the bottom of the picture, lined up with the left side of the panel. It is for the source of the numbers:
data(gapminder_2007) + point + x(gdp, scale = "log") + y(life) +
color(continent) + title("People in richer countries live longer") +
subtitle("Life expectancy against GDP per person, 142 countries, 2007") +
caption("Source: Gapminder Foundation")(data(gapminder_2007) + point + x(col.gdp, scale = "log") + y(col.life) +
color(col.continent) + title("People in richer countries live longer") +
subtitle("Life expectancy against GDP per person, 142 countries, 2007") +
caption("Source: Gapminder Foundation"))data(gapminder_2007) + point + x(:gdp, scale = "log") + y(:life) +
color(:continent) + title("People in richer countries live longer") +
subtitle("Life expectancy against GDP per person, 142 countries, 2007") +
caption("Source: Gapminder Foundation")plot(data(gapminder_2007), point, x(col.gdp, { scale: "log" }),
y(col.life), color(col.continent),
title("People in richer countries live longer"),
subtitle("Life expectancy against GDP per person, 142 countries, 2007"),
caption("Source: Gapminder Foundation"))“Given gapminder 2007: points, x is gdp on a log scale, y is life, color by continent.”
Each of the three makes the panel shorter by the height of its line. At the default size of 800 by 600 pixels, the three together take about 60 pixels.
2.14 Facets: one panel per category
When one plot shows too many groups to compare, a facet gives each group a panel of its own.
| Facet | What it cuts |
|---|---|
facet |
Splits one plot into panels, one per category of the column it names |
A facet is written on the right of | or /, which is why you have already seen it drawn in the operator section: … | facet(continent). See Faceting.
2.15 Selections: choosing rows in a drawn plot
A plot that is already on the screen can still answer a question, if you can select part of it.
| Selection | What it does |
|---|---|
brush |
Dims the rows you did not pick, and removes none of them |
A selection is the only kind that acts on a plot after it is drawn. Dragging changes what you see. It never changes the sentence, so running the sentence again gives you the selection it names, not the one you dragged. See Selection.
2.16 Names the host language already uses
Every word in the kernel is an ordinary English word, and many ordinary words already name something else. mean names a statistic in every language that has one. data, order, range, text and box all name something in R before gog is loaded. That is a consequence of choosing plain names rather than a sign that something has gone wrong, and in ordinary code almost nothing changes.
When a word is both gog’s and the language’s own, the language decides which one your call reaches. gog does not change that, and each language decides differently, so the answer is different in each one:
| Language | What the language does | To reach the other one |
|---|---|---|
| R | Twenty kernel words collide with R names. Nine are objects, and a call skips them; eleven are functions, and those mask R’s own | base::order(x), stats::density(x) |
| Python | A star-import rebinds six builtins, because Python has no rule that protects them | from builtins import sum |
| Julia | Two modules exporting one name is an error, never a silent choice | Base.sum([1, 2, 3]) |
| JavaScript | Nothing happens, because no name reaches your scope unless you import it | nothing to do |
R’s case is the one to understand. Twenty kernel words collide with R names, but only five of them can give you a wrong result with no message: data, box, title, order and palette. The other fifteen are safe, for two different reasons.
Nine of the twenty are plain objects, not functions. Most marks take no argument, and neither does a statistic like mean or max. When R evaluates a call, it looks for a function of that name and skips any name whose value is not a function. So base R still answers mean(c(1, 2, 3)), and sapply(df, mean) works for the same reason. When gog loads, R prints a message listing the names gog masks. It lists only the eleven functions, because an ordinary call can reach only a function.
The other eleven take arguments, so they are functions, and they do mask R’s own. Once gog loads, range(0.25, 0.75) is gog’s range transform, the band between two quantiles; to reach base R’s, write base::range(). Suppose you forget and write range(heights) to get the smallest and largest values of a vector. The call reaches gog’s range(), which takes two numbers between 0 and 1, not a vector, so it refuses the call and names the base R function you meant, base::range(). Six of the eleven functions catch a mistake this way. The other five cannot. data(mtcars), meant to load R’s example dataset, is also correct gog that names a plot’s table, so gog answers it and cannot warn you. box(), title("..."), order(key) and palette("Set1") are the same. A refusal needs a mistake the engine can detect, and none of these calls contains one.
There is no way to remove the collision without losing something. Prefixing the vocabulary, gog_mean and gog_range, would remove the reason for the vocabulary: a reader who has to learn a prefix learns every word twice, and none of the words is English any more. Renaming order to sort_by would take away a word every reader already knows. So the words stay, and the few places that need a qualified name write one.
Each binding chapter carries three things for its language: the full list of collisions, that language’s rule from the table above as code you can run, and the few calls the rule does not cover. In R there are five such calls, and they fail for two reasons. Three hand a word to one function that passes it to a second, the way aggregate(v ~ g, df, mean) passes mean on. Two use the word as a value rather than calling it, as do.call(sum, list(x)) does. See R, Python, Julia and JavaScript.
2.17 In short
The kernel has eleven kinds of word: five build a plot, and six refine or arrange one. The minimum sentence is a table, a mark, and that mark’s required positions. A sentence gog cannot draw is refused, and the refusal says what to write instead. Most of +’s order is free, with two exceptions: the first mark is drawn at the bottom, and a channel applies to every layer only when it comes before every mark (Encoding scope).
2.18 What comes next
The kernel is the whole vocabulary, but knowing the words is not yet using them easily. Reading practice gives you ten plots written in these words, and Writing practice leaves the writing to you.