| 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 |
8 Point
How does one measurement change as another changes? point draws one glyph, a small symbol, at the (x, y) position of each row. The glyph is a dot unless you choose another.
The plot it makes is the scatterplot, and it was invented later than the other basic charts. Playfair published the line chart and the bar chart in 1786 (Playfair, 1786), and the pie chart in 1801 (Playfair, 1801). The scatterplot came about thirty years after the pie chart. A picture that plots one measured column against another is usually dated to John Herschel. In 1833 he plotted the position of twin stars against the year, then drew a smooth curve through the dots so the trend could be seen (Friendly & Denis, 2005). Other names for the same picture are scatter diagram and scattergram. When size maps a third column, the same plot is called the bubble chart.
None of those chart names is a word in gog, and that is deliberate. point is a mark, not a chart. point + x(gdp) + y(life) is a scatterplot, and point + x(continent) + y(life) is a strip plot. The mark is the same in both. The chart name belongs to the whole sentence, not to any word in the vocabulary. That is why the names live in an appendix, Chart names, instead of in the kernel.
8.1 Basic usage
Everything in this chapter is added to one sentence, the smallest that draws a point: a source, the mark, and its two positions.
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.”
Each dot is one row, one country, placed by its gdp and its life. Nothing else about the row is shown yet. Both columns hold numbers, so each axis is a linear scale. Most of the dots sit on the left, because most countries have far lower incomes than the richest few. The Scales chapter shows the log scale that spreads them out.
8.2 The dot as a default
Write point and you get a dot, and treating it as the dot mark is right almost every time. But the dot is only a default, and you can change it. A point is a glyph at a position, and shape replaces the dot with another of the five glyphs gog draws, one per category. Shape below names the five, says which category gets which, and what to do with more than five.
data(gapminder_2007) + point + x(gdp) + y(life) + shape(continent) +
title("The glyph is not always a dot")(data(gapminder_2007) + point + x(col.gdp) + y(col.life) + shape(col.continent) +
title("The glyph is not always a dot"))data(gapminder_2007) + point + x(:gdp) + y(:life) + shape(:continent) +
title("The glyph is not always a dot")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
shape(col.continent), title("The glyph is not always a dot"))“Given gapminder 2007: points, x is gdp, y is life, shape by continent.”
So “a dot” means “the first of the five shapes, drawn when you name no shape.” That is what makes point and text siblings: both draw one glyph per row at (x, y), differing only in where the glyph comes from. A point picks its glyph from the fixed set of five, with shape. A text mark takes its glyph from a column, with label, so what you see is the value in that column. Put both on one plot and each row is located by a dot and named by its label:
| 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) + point + style(size = 5, color = "#9e9e9e") +
text + x(gold) + y(silver) + label(country) + style(nudge = "up") +
x_label("Gold") + y_label("Silver") +
title("point and text, siblings on one plot")(data(medals) + point + style(size = 5, color = "#9e9e9e") +
text + x(col.gold) + y(col.silver) + label(col.country) + style(nudge = "up") +
x_label("Gold") + y_label("Silver") +
title("point and text, siblings on one plot"))data(medals) + point + style(size = 5, color = "#9e9e9e") + text +
x(:gold) + y(:silver) + label(:country) + style(nudge = "up") +
x_label("Gold") + y_label("Silver") +
title("point and text, siblings on one plot")plot(data(medals), point, style({ size: 5, color: "#9e9e9e" }), text,
x(col.gold), y(col.silver), label(col.country), style({ nudge: "up" }),
x_label("Gold"), y_label("Silver"),
title("point and text, siblings on one plot"))“Given the medals: points and also text, x is gold, y is silver, label by country.”
8.3 A categorical x: the strip plot
x accepts a categorical column as readily as a continuous one. Each category gets a position on the axis, and every row is drawn there. You see the whole distribution rather than a summary of it:
data(gapminder_2007) + point + x(continent) + y(life) +
y_label("Life expectancy") + title("One dot per country")(data(gapminder_2007) + point + x(col.continent) + y(col.life) +
y_label("Life expectancy") + title("One dot per country"))data(gapminder_2007) + point + x(:continent) + y(:life) +
y_label("Life expectancy") + title("One dot per country")plot(data(gapminder_2007), point, x(col.continent), y(col.life),
y_label("Life expectancy"), title("One dot per country"))This is the same rule bar uses for a categorical axis, and no mark gets a special case. Compare it with bar * mean, which collapses each group to one number:
data(gapminder_2007) + bar * mean + x(continent) + y(life) +
y_label("Mean life expectancy") + title("The same data, summarized")(data(gapminder_2007) + bar * mean + x(col.continent) + y(col.life) +
y_label("Mean life expectancy") + title("The same data, summarized"))data(gapminder_2007) + bar * mean + x(:continent) + y(:life) +
y_label("Mean life expectancy") + title("The same data, summarized")plot(data(gapminder_2007), layer(bar, mean), x(col.continent),
y(col.life), y_label("Mean life expectancy"),
title("The same data, summarized"))“Given gapminder 2007: bars derived by mean, x is continent, y is life.”
The strip plot shows Africa’s spread and its overlap with Asia; the bar chart shows neither. The strip plot has one weakness: every point in a category sits on the same vertical line, so heavy overlap can hide how many there are. opacity makes each point partly transparent, so overlap reads as a darker patch. point * jitter spreads overlapping points along the category axis, never off their measured value.
A second answer is to count the overlapping points instead of separating them. point * bin * stack stacks one dot per row along the measure axis, which is the dot plot. The cookbook draws it with thirty rows, where every row still shows. Which to write is your choice, by what you want the reader to see. jitter keeps the spread: every value sits where it was measured. bin * stack gives the count: how many rows fall in each bin, which is close to a bar chart, with the rows still visible instead of summarized into one length.
8.3.1 Either axis: a horizontal strip
gog reads the direction of a plot from its bindings: whichever axis carries the category decides it, for a point as for a bar. So the category can sit on y instead of x, and the strips lie down. The countries and the distributions are the same, and each row now reads left to right:
data(gapminder_2007) + point + x(life) + y(continent) +
style(opacity = 0.6) +
x_label("Life expectancy") +
title("The same distributions, lying down")(data(gapminder_2007) + point + x(col.life) + y(col.continent) +
style(opacity = 0.6) +
x_label("Life expectancy") +
title("The same distributions, lying down"))data(gapminder_2007) + point + x(:life) + y(:continent) +
style(opacity = 0.6) + x_label("Life expectancy") +
title("The same distributions, lying down")plot(data(gapminder_2007), point, x(col.life), y(col.continent),
style({ opacity: 0.6 }), x_label("Life expectancy"),
title("The same distributions, lying down"))If you know ggplot2’s coord_flip(), there is no such word here: swapping the two bindings was the whole change, as swapping the two arguments of matplotlib’s scatter() is. Use the horizontal form when the category names are too long for the bottom axis. It also helps when there are many categories, since a list of rows is easier to read than a list of columns.
8.4 Color
Which continent is which? Every dot looks the same until a column tells them apart. Bind color to a categorical column to color points by group. gog assigns a distinct color from a 20-color palette automatically.
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.”
A color legend is generated automatically whenever a column is mapped to color.
8.5 Shape
shape maps a categorical column to one of five glyphs. In order, they are the circle, the square, the triangle, the diamond and the cross, one per category. Categories are taken as they first appear in the table, or in the order you declare, so the first category gets the circle, the second the square, and so on. Asia comes first in the gapminder table, so Asia is the circle, and the legend below reads the same way. Five is the whole set, and about as many as a reader can separate on one plot. Do not map shape to a column with more than five categories: the sixth starts over at the circle, two categories then share a glyph and look the same, and gog does not warn. The book shows four ways to draw more groups than that:
color, whose default palette has twenty colors, as Color above does with the continents.- One panel per category with
facet, folded into rows so the panels stay wide: Folding a long line of panels. - The name on the point instead of a glyph: a
textlayer withlabel, the labeled scatter, andrepelwhen the names overlap. This is the one route that reaches a hundred categories, and in the web edition a brush leaves only the selected names readable. - Every category in one pale color, with the few that matter drawn over it in a second layer: What settings are for.
A column with fewer categories often carries the encoding: continent groups the countries and fits the five glyphs, so country goes on the label or the facet.
Combine color and shape on the same column so that colorblind readers, and black-and-white printing, can still separate the groups:
data(gapminder_2007) + point + x(gdp) + y(life) +
color(continent) + shape(continent)(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
color(col.continent) + shape(col.continent))data(gapminder_2007) + point + x(:gdp) + y(:life) + color(:continent) +
shape(:continent)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
color(col.continent), shape(col.continent))8.6 Size
A third measurement has nowhere to go once both positions are taken. size gives it one: it maps a continuous column to the point radius, from 3 pixels at the smallest value to 12 at the largest.
data(gapminder_2007) + point + x(gdp) + y(life) +
color(continent) + size(population)(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
color(col.continent) + size(col.population))data(gapminder_2007) + point + x(:gdp) + y(:life) + color(:continent) +
size(:population)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
color(col.continent), size(col.population))“Given gapminder 2007: points, x is gdp, y is life, color by continent, size by population.”
A size legend showing the minimum, midpoint, and maximum of the column is generated automatically.
8.7 Opacity
opacity maps a continuous column to transparency. It helps in a dense scatterplot, where marks cover one another and hide the shape of the data. Population is skewed, so on a linear scale two countries are dark and the rest are faint. The log scale spreads them across the range:
data(gapminder_2007) + point + x(gdp) + y(life) +
opacity(population, scale = "log") +
title("Opacity: population, on a log scale")(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
opacity(col.population, scale = "log") +
title("Opacity: population, on a log scale"))data(gapminder_2007) + point + x(:gdp) + y(:life) +
opacity(:population, scale = "log") +
title("Opacity: population, on a log scale")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
opacity(col.population, { scale: "log" }),
title("Opacity: population, on a log scale"))“Given gapminder 2007: points, x is gdp, y is life, opacity by population on a log scale.”
See Channels and legends for the opacity range and why line does not accept this channel.
That example maps opacity to a column. Often there is no column to show: the only problem is that many points overlap. Then you want one opacity on every point instead. That is a setting rather than a mapping, so you write style(opacity = ) and no legend appears; see Setting vs mapping. The table below, gm_all, holds every gapminder country in every year, so the points overlap heavily:
| country | continent | year | life | population | gdp |
|---|---|---|---|---|---|
| Afghanistan | Asia | 1952 | 28.801 | 8425333 | 779.4453 |
| Afghanistan | Asia | 1957 | 30.332 | 9240934 | 820.8530 |
| Afghanistan | Asia | 1962 | 31.997 | 10267083 | 853.1007 |
| Afghanistan | Asia | 1967 | 34.020 | 11537966 | 836.1971 |
| Afghanistan | Asia | 1972 | 36.088 | 13079460 | 739.9811 |
data(gm_all) + point + x(gdp) + y(life) +
style(opacity = 0.2, size = 3) +
title("Every point at one opacity")(data(gm_all) + point + x(col.gdp) + y(col.life) +
style(opacity = 0.2, size = 3) +
title("Every point at one opacity"))data(gm_all) + point + x(:gdp) + y(:life) +
style(opacity = 0.2, size = 3) + title("Every point at one opacity")plot(data(gm_all), point, x(col.gdp), y(col.life),
style({ opacity: 0.2, size: 3 }), title("Every point at one opacity"))“Given all the gapminder years: points, x is gdp, y is life, with opacity 0.2.”
8.8 All channels at once
A table often has more to show than two positions can hold. The refining channels carry the rest, and they can all be on one plot at once. Here continent goes to color and shape together, so the groups survive black-and-white printing, and population goes to size:
data(gapminder_2007) + point + x(gdp) + y(life) +
color(continent) +
shape(continent) +
size(population) +
title("GDP, life expectancy, and population")(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
color(col.continent) +
shape(col.continent) +
size(col.population) +
title("GDP, life expectancy, and population"))data(gapminder_2007) + point + x(:gdp) + y(:life) + color(:continent) +
shape(:continent) + size(:population) +
title("GDP, life expectancy, and population")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
color(col.continent), shape(col.continent), size(col.population),
title("GDP, life expectancy, and population"))8.9 What you can set
A setting changes how every point looks without reading a column, and each mark takes its own. These are a point’s, with the values each accepts:
| Setting | Value |
|---|---|
style(color = ) |
any CSS color name or hex |
style(opacity = ) |
0 to 1 |
style(size = ) |
pixels |
style(shape = ) |
circle, square, triangle, diamond, cross |
style(border_color = ) |
any CSS color name or hex |
style(border_size = ) |
pixels |
A point is a closed glyph, a shape with an inside to fill, so it takes more settings than most marks. It is the only mark that picks a shape, and one of the five marks that draw a border. size is the radius in pixels, and border_* draws that border distinct from the fill.
A point layer can carry several of them at once:
data(gapminder_2007) + point + x(gdp) + y(life) +
style(shape = "diamond", size = 7, color = "steelblue",
border_color = "navy", border_size = 1.5) +
x_label("GDP per person") + y_label("Life expectancy") +
title("A shape, a size, and a border")(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
style(shape = "diamond", size = 7, color = "steelblue",
border_color = "navy", border_size = 1.5) +
x_label("GDP per person") + y_label("Life expectancy") +
title("A shape, a size, and a border"))data(gapminder_2007) + point + x(:gdp) + y(:life) +
style(shape = "diamond", size = 7, color = "steelblue", border_color = "navy", border_size = 1.5) +
x_label("GDP per person") + y_label("Life expectancy") +
title("A shape, a size, and a border")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
style({ shape: "diamond", size: 7, color: "steelblue",
border_color: "navy", border_size: 1.5 }), x_label("GDP per person"),
y_label("Life expectancy"), title("A shape, a size, and a border"))“Given gapminder 2007: points, x is gdp, y is life, colored steelblue, with shape diamond, size 7, border color navy and border size 1.5.”
The navy border is what separates the overlapping points, which is what a border is for in a dense cloud. A cross has no fill to outline, so border_color and border_size change nothing on that shape.
pattern is not on the list. A texture needs an area to fill or a stroke to dash, and a small glyph has neither, so a point’s only form is its shape; What it refuses shows the refusal. When points overlap, a border keeps them apart, and so does jitter, one of the four collision modifiers: point * jitter(0.4) moves each point a little along the categorical axis, by that amount, as the strip plot showed.
The grid of every mark and every setting shows which other marks share this list. What a mark maps rather than sets is its row on the companion grid.
8.10 What it refuses
A point is a glyph, and the two refusals below both follow from that.
size shows an amount, so it needs a column of amounts:
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.
Continents are names, not amounts, and a larger dot would claim an order the data does not have. The refusal names three channels that separate categories: color, shape and pattern. A point takes the first two, and the next section shows why it refuses pattern.
The second refusal is texture. A pattern is a stroke’s dash or a fill’s hatch, and a small closed glyph is neither:
data(gapminder_2007) + point + x(gdp) + y(life) + style(pattern = "dashed")data(gapminder_2007) + point + x(col.gdp) + y(col.life) + style(pattern = "dashed")data(gapminder_2007) + point + x(:gdp) + y(:life) +
style(pattern = "dashed")plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
style({ pattern: "dashed" }))Error:
! gog: `style(pattern = )` is a stroke's dash or a fill's texture, and a `point` is a glyph, not either — a point's form is set by `shape`, a text's by its content. The strokes (`line`/`step`/`interval`/`path`/`rule`/`edge`) take a dash; the fills (`area`/`bar`/`box`/`ribbon`/`zone`) a texture.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
Both messages send you to shape, which is the one form a point can change.