| 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. William Playfair published the line chart and the bar chart in 1786 (Playfair, 1786), and the pie chart in 1801 (Playfair, 1801). The first scatterplot was drawn about thirty years after the pie chart. Historians usually name John Herschel as the first person to plot one measured column against another. In 1833 he plotted the position of a double star 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 are listed in an appendix, Chart names, instead of in the kernel.
8.1 Basic usage
Do people in richer countries live longer? Most examples in this chapter start from the smallest sentence 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. The answer to the opening question is yes: life expectancy rises quickly with gdp among the poorer countries, then slowly among the richer ones. Both columns hold numbers, so each axis is a linear scale. Most of the dots are 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. Calling it the dot mark is accurate 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 one of the seven glyphs gog draws, a different one for each category of a column. Shape below names the seven, says which category gets which, and what to do with more than seven.
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 circle, the first of the seven glyphs, 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 seven, 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.”
The dot shows where each country is, and the label names it. style(nudge = "up") moves each label above its dot, so the two do not cover each other.
8.3 A categorical x: the strip plot
Every plot so far has put a measurement on x, but x also accepts a categorical column. Each category gets its own slot on the axis, and every row is drawn in its category’s slot. 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"))Every mark places a categorical column this way, bar included. Compare the strip plot with bar * mean, which reduces 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.”
mean is not the bar’s own transform. Other marks take it too, and each draws the same means in its own way:
Mark derived by mean |
What it draws |
|---|---|
point * mean |
one dot at each mean |
line * mean |
the means joined into a profile |
area * mean |
the same profile, filled to the baseline |
step * mean |
each mean held flat until the next category |
bar * mean |
one bar per category, the plot above |
median, sum, min and max can replace mean in every row of that table. So can quantile, which needs a probability: quantile(0.9) is the value nine tenths of each group falls below. The statistic decides the number, and the mark decides how it is drawn.
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 is on the same vertical line. Where many countries have close values, their dots cover each other, and you cannot see how many there are. opacity makes each point partly transparent, so overlap reads as a darker patch. point * jitter moves overlapping points sideways along the categorical axis, and never changes their measured value:
data(gapminder_2007) + point * jitter + x(continent) + y(life) +
y_label("Life expectancy") + title("The same dots, spread sideways")(data(gapminder_2007) + point * jitter + x(col.continent) + y(col.life) +
y_label("Life expectancy") + title("The same dots, spread sideways"))data(gapminder_2007) + point * jitter + x(:continent) + y(:life) +
y_label("Life expectancy") + title("The same dots, spread sideways")plot(data(gapminder_2007), layer(point, jitter), x(col.continent),
y(col.life), y_label("Life expectancy"),
title("The same dots, spread sideways"))“Given gapminder 2007: points derived by jitter, x is continent, y is life.”
Every dot keeps its life expectancy, so its height has not changed. Only its place inside its continent’s slot has changed, and that place means nothing: the dot still reads as its continent. jitter moves a dot’s category, never its value. In a scatterplot both axes are measurements, so a moved dot would show a value the data does not hold. gog therefore refuses point * jitter when x and y are both continuous, as What it refuses shows.
A third answer, point * dodge, also moves points sideways within their slot. Unlike jitter, it moves each point only as far as it must to touch no other point:
data(gapminder_2007) + point * dodge + x(continent) + y(life) +
y_label("Life expectancy") + title("The same dots, set apart until none touch")(data(gapminder_2007) + point * dodge + x(col.continent) + y(col.life) +
y_label("Life expectancy") + title("The same dots, set apart until none touch"))data(gapminder_2007) + point * dodge + x(:continent) + y(:life) +
y_label("Life expectancy") +
title("The same dots, set apart until none touch")plot(data(gapminder_2007), layer(point, dodge), x(col.continent),
y(col.life), y_label("Life expectancy"),
title("The same dots, set apart until none touch"))“Given gapminder 2007: points derived by dodge, x is continent, y is life.”
No dot covers another, and every dot keeps its life expectancy. Where many countries have close values, the dots spread wide, so the outline of each group shows the shape of its distribution, one dot per country. This plot is the beeswarm. When a slot is too crowded to hold its dots, gog draws the extra ones at the slot’s edge, on top of one another, and a warning says how many overlap. It never moves them into the next continent’s slot.
A fourth answer is to count the overlapping points instead of separating them. point * bin * stack cuts the measured column into bins and piles one dot per row above each bin, which is the dot plot. It shows one distribution, with the measure on x; | facet(continent) draws one for each continent. The cookbook draws it with thirty rows, where every row is still visible. Choose among jitter, dodge and bin * stack by what you want the reader to see. jitter and dodge keep the spread: every value is drawn where it was measured, and with dodge no dot covers another unless its slot is full. bin * stack gives the count: how many rows fall in each bin. The result looks like a bar chart, but each row is still its own dot.
8.3.1 Either axis: a horizontal strip
gog reads the direction of a plot from its bindings. Whichever axis carries the category decides the direction, for a point exactly as for a bar. So the category can be on y instead of x, and the strips run horizontally. The countries and the distributions are the same, and each continent’s dots now run from 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, the same way you swap the two arguments of matplotlib’s scatter(). Use the horizontal form when the category names are too long for the bottom axis. It also helps when there are many categories, since names listed down the left side are easier to read than names crowded along the bottom.
With one row per category, the horizontal strip is the Cleveland dot plot. It shares a name with the dot plot above but is a different chart: there each dot is one row in a bin, and here each dot is one category’s value. Comparing categories draws it for twelve countries.
A point derived by count can be horizontal too. point * count counts the rows in each category and draws one point for each. With the category on y, that is a Cleveland dot plot of the counts:
data(gapminder_2007) + point * count + y(continent) + x_label("Countries") +
title("How many countries each continent holds")(data(gapminder_2007) + point * count + y(col.continent) + x_label("Countries") +
title("How many countries each continent holds"))data(gapminder_2007) + point * count + y(:continent) +
x_label("Countries") + title("How many countries each continent holds")plot(data(gapminder_2007), layer(point, count), y(col.continent),
x_label("Countries"), title("How many countries each continent holds"))“Given gapminder 2007: points derived by count, y is continent.”
Africa holds the most countries, 52, and Oceania the fewest, 2. A point derived by bin, proportion or density follows the same rule, as a bar does. Name the column on y, and the count, share or density is drawn along x.
8.4 Color
In the scatterplot of gdp against life, every dot looks the same, so you cannot see which continent each country is in. 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.”
Most African countries are at the lower left, and most European countries at the upper right. gog adds a color legend whenever you map a column to color.
8.4.1 Splitting without color
Sometimes the color belongs to another layer, such as boxes colored by era, and the points should be split the same way without being painted. color does two things at once: it splits the points into groups, and it paints each group. group does only the first. On points with no statistic and no dodge, splitting changes nothing, because each row is already its own point, and gog refuses it (What it refuses). With dodge or a statistic, group decides which rows go together, and the other layer shows the colors. The gm_eras table holds each country’s life expectancy in 1957 and in 2007:
| country | continent | year | life | population | gdp | era |
|---|---|---|---|---|---|---|
| Afghanistan | Asia | 1957 | 30.332 | 9240934 | 820.8530 | 1957 |
| Afghanistan | Asia | 2007 | 43.828 | 31889923 | 974.5803 | 2007 |
| Albania | Europe | 1957 | 59.280 | 1476505 | 1942.2842 | 1957 |
| Albania | Europe | 2007 | 76.423 | 3600523 | 5937.0295 | 2007 |
| Algeria | Africa | 1957 | 45.685 | 10270856 | 3013.9760 | 1957 |
Here the boxes are colored by era, and point * dodge draws one dot per row, so each country appears twice, once for each era. group(era) gives each era its own half of the continent’s slot, as dodge does for the boxes, so each era’s dots are drawn over that era’s box:
data(gm_eras) + box * dodge + color(era) +
point * dodge + group(era) + style(size = 2.5) +
x(continent) + y(life)(data(gm_eras) + box * dodge + color(col.era) +
point * dodge + group(col.era) + style(size = 2.5) +
x(col.continent) + y(col.life))data(gm_eras) + box * dodge + color(:era) + point * dodge + group(:era) +
style(size = 2.5) + x(:continent) + y(:life)plot(data(gm_eras), layer(box, dodge), color(col.era),
layer(point, dodge), group(col.era), style({ size: 2.5 }),
x(col.continent), y(col.life))“Given the gapminder eras: boxes derived by dodge, colored by era, and also points derived by dodge, grouped by era, with size 2.5, x is continent, y is life.”
Without group(era), each continent’s dots would form one beeswarm in the middle of its slot, between its two boxes. style(size = 2.5) draws smaller dots, so more of them fit in each half of a crowded slot.
A statistic is split the same way. point * mean + group(era) computes one mean for each era within each continent. Here colored lines join each era’s means across the continents, and black dots mark the means themselves:
data(gm_eras) + line * mean + color(era) +
point * mean + group(era) + style(color = "black") +
x(continent) + y(life)(data(gm_eras) + line * mean + color(col.era) +
point * mean + group(col.era) + style(color = "black") +
x(col.continent) + y(col.life))data(gm_eras) + line * mean + color(:era) + point * mean + group(:era) +
style(color = "black") + x(:continent) + y(:life)plot(data(gm_eras), layer(line, mean), color(col.era), layer(point, mean),
group(col.era), style({ color: "black" }), x(col.continent),
y(col.life))“Given the gapminder eras: lines derived by mean, colored by era, and also points derived by mean, grouped by era, colored black, x is continent, y is life.”
Without group(era), point * mean would compute one mean for each continent from both eras, and each dot would be between the two lines.
8.5 Shape
Which glyph does each category get, and what happens when there are more categories than glyphs? shape maps a categorical column to one of seven glyphs. In order, they are the circle, the square, the triangle, the diamond, the cross, the star and the wye, which looks like the letter Y. The categories are ordered by where each first appears in the table, or by the order you declare. The first category gets the circle, the second the square, and so on. Here is the whole set, in the order gog assigns them:
glyphs <- data.frame(
glyph = c("circle", "square", "triangle", "diamond", "cross", "star", "wye"),
row = c(1, 1, 1, 1, 1, 1, 1)
)
data(glyphs) + point + x(glyph) + y(row) + shape(glyph) +
style(size = 8) + x_label("") + y_label("") +
title("The seven glyphs, in the order they are assigned")glyphs = {
"glyph": ["circle", "square", "triangle", "diamond", "cross", "star", "wye"],
"row": [1, 1, 1, 1, 1, 1, 1],
}
(data(glyphs) + point + x(col.glyph) + y(col.row) + shape(col.glyph) +
style(size = 8) + x_label("") + y_label("") +
title("The seven glyphs, in the order they are assigned"))glyphs = (
glyph = ["circle", "square", "triangle", "diamond", "cross", "star", "wye"],
row = [1, 1, 1, 1, 1, 1, 1],
)
data(glyphs) + point + x(:glyph) + y(:row) + shape(:glyph) +
style(size = 8) + x_label("") + y_label("") +
title("The seven glyphs, in the order they are assigned")const glyphs = {
glyph: ["circle", "square", "triangle", "diamond", "cross", "star", "wye"],
row: [1, 1, 1, 1, 1, 1, 1],
};
plot(data(glyphs), point, x(col.glyph), y(col.row), shape(col.glyph),
style({ size: 8 }), x_label(""), y_label(""),
title("The seven glyphs, in the order they are assigned"))“Given the glyphs table: points, x is glyph, y is row, shape by glyph, with size 8.”
Asia comes first in the gapminder table, so Asia is the circle, and every continent legend in this chapter lists Asia first. Seven is the whole set gog draws. It is also about as many glyphs as a reader can separate on one plot. Map shape to a column with more than seven categories and the eighth category gets the circle again, so two categories share a glyph and look the same. gog says so when that happens, naming the count and the limit, and the plot still draws. The nutrients table has eight foods, one more than there are glyphs:
| food | nutrient | amount |
|---|---|---|
| salmon | protein | 25.4 |
| salmon | fat | 12.4 |
| salmon | carbs | 0.0 |
| salmon | fiber | 0.0 |
| salmon | iron | 0.5 |
data(nutrients) + point + x(nutrient) + y(amount) + shape(food) +
y_label("Amount per 100g") + title("Eight foods, seven glyphs")(data(nutrients) + point + x(col.nutrient) + y(col.amount) + shape(col.food) +
y_label("Amount per 100g") + title("Eight foods, seven glyphs"))data(nutrients) + point + x(:nutrient) + y(:amount) + shape(:food) +
y_label("Amount per 100g") + title("Eight foods, seven glyphs")plot(data(nutrients), point, x(col.nutrient), y(col.amount),
shape(col.food), y_label("Amount per 100g"),
title("Eight foods, seven glyphs"))gog: `shape(food)` has 8 categories and `point` draws 7 glyphs, so the set starts over: 1 category shares a glyph with an earlier one. Use `color(food)`, whose default palette has twenty, or give each category its own panel with `facet(food)`.
“Given the nutrients: points, x is nutrient, y is amount, shape by food.”
Oats is the eighth food in the table, so its points are circles, the glyph salmon already has. Nothing in the picture shows which circles are salmon and which are oats, so the message counts the foods and the glyphs for you. The book has four ways to draw more than seven groups:
color, whose default palette has 20 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. Of the four, only this one can name each of a hundred categories. In the web edition, a brush leaves only the selected names readable, as long as thetextlayer does not userepel. - Every category in one pale color, and the few that matter drawn again on top, in a second layer: What settings are for.
When a table has two categorical columns, map shape to the one with fewer categories. continent has five, and seven glyphs are enough for five. country has 142, so it goes on a label or a facet instead.
Some readers cannot see the difference between colors, and a black-and-white printout removes it. Map color and shape to the same column, and the glyphs 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))Each continent now differs in two ways, so either one alone still separates the groups.
8.6 Size
Once x and y are both bound, no position is free for a third measurement. The size channel can show it instead. size maps a continuous column to the point’s 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.”
The two largest dots are China and India, and both are at the left, among the poorer countries. gog adds a size legend showing the column’s minimum, midpoint, and maximum.
Two transforms in a chain can supply the number on y and the number on size. After count, the cumulative transform adds each continent’s count to the counts before it, and the point is drawn at that total. count still holds each continent’s own count, so size(count) can show it:
data(gapminder_2007) + point * count * cumulative + x(continent) + size(count) +
title("Running count of countries, sized by each continent's count")(data(gapminder_2007) + point * count * cumulative + x(col.continent) + size(col.count) +
title("Running count of countries, sized by each continent's count"))data(gapminder_2007) + point * count * cumulative + x(:continent) +
size(:count) +
title("Running count of countries, sized by each continent's count")plot(data(gapminder_2007), layer(point, count, cumulative),
x(col.continent), size(col.count),
title("Running count of countries, sized by each continent's count"))“Given gapminder 2007: points derived by count and cumulative, x is continent, size by count.”
The last point is at 142, every country in the table. Africa’s point is the largest, because Africa adds 52 countries, the most of any continent. Oceania’s point is the smallest: it adds only 2 to the total.
8.7 Opacity
The horizontal strip plot above made every point partly transparent with one setting, style(opacity = 0.6). opacity is also a channel: a larger value in a continuous column gives a more opaque point. Population is skewed, so a linear scale would leave China and India dark and the rest faint. The log scale spreads the values 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 gm_all table 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.”
Where many points overlap, the color is darker, and the darkest area is at the left, where most countries were in most years. A hollow point is the other way to show the same crowding, and What you can set draws this table with it.
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 is mapped to both color and shape, so the groups stay separate in black-and-white printing, and population is mapped 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"))Four columns now show in one picture: gdp and life by position, continent by color and glyph, and population by size.
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 settings, 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, star, wye |
style(border_color = ) |
any CSS color name or hex |
style(border_size = ) |
pixels |
A point takes more settings than most marks. It is the only mark that can set shape. Six of its seven glyphs are filled with color, so a point is also one of the marks that draw a border. size is the radius in pixels, and border_color and border_size give that border its own color and width.
A point layer can carry several settings 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.”
Where two diamonds overlap, the navy border shows where one ends and the next begins. That is what a border is for where many points crowd together. A cross has no fill to outline, so border_color and border_size change nothing on that glyph.
A border can also be the whole point. Set the fill color to "none" and give the border a color, and each point is drawn hollow: an outline with no fill. A hollow point can be partly transparent as well. opacity fades what color paints, and color = "none" paints nothing, so gog refuses opacity on a hollow point. Put the transparency in the border’s color instead. In "#4682b44d", the first six hex digits are steelblue, and the last two, 4d, make the outline 30% opaque. Here are all 1,704 rows of gm_all again, the table the Opacity section drew at one opacity:
data(gm_all) + point + x(gdp) + y(life) +
style(color = "none", border_color = "#4682b44d", size = 3) +
title("Every point hollow, its outline partly transparent")(data(gm_all) + point + x(col.gdp) + y(col.life) +
style(color = "none", border_color = "#4682b44d", size = 3) +
title("Every point hollow, its outline partly transparent"))data(gm_all) + point + x(:gdp) + y(:life) +
style(color = "none", border_color = "#4682b44d", size = 3) +
title("Every point hollow, its outline partly transparent")plot(data(gm_all), point, x(col.gdp), y(col.life),
style({ color: "none", border_color: "#4682b44d", size: 3 }),
title("Every point hollow, its outline partly transparent"))Where hollow points overlap, every outline stays visible, and where many pile up, the outlines add up to a darker area. The plot shows each point and the crowding at once. Neither this nor opacity moves a point off its measured value, which is why they, and not jitter, are the answers for a crowded scatterplot. On a categorical axis, jitter and dodge move points apart instead, as the strip plot showed. point * jitter(0.4) spreads them 0.4 times as wide as a plain jitter.
pattern is not on the list. A hatch needs a large area and a dash needs a long stroke, and a glyph is too small for either. To separate groups by glyph, a point uses shape instead, and What it refuses shows the refusal.
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
Five refusals follow, each with its reason: a category on size, any pattern, jitter or dodge on a scatterplot, opacity on a hollow point, and group on points with no statistic or dodge.
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` or `shape` 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 message suggests color or shape instead: each continent gets its own color or glyph.
The second refusal is about texture. A pattern is a dash along a stroke or a hatch across an area, and a glyph is too small for either:
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.
The message names shape, which chooses a point’s glyph.
The third refusal is jitter on a scatterplot. In a strip plot, jitter moves a dot sideways inside its continent’s slot. A place inside a slot has no scale, so the move changes nothing a reader could measure. In a scatterplot both axes are measurements, and every place on them is a value. A jittered dot would be drawn at a gdp and a life expectancy that are not in the table. Two countries with the same values would no longer look the same, and a reader who reads a dot against the axes would read a number that was never measured. So gog refuses:
data(gapminder_2007) + point * jitter + x(gdp) + y(life)data(gapminder_2007) + point * jitter + x(col.gdp) + y(col.life)data(gapminder_2007) + point * jitter + x(:gdp) + y(:life)plot(data(gapminder_2007), layer(point, jitter), x(col.gdp), y(col.life))Error:
! gog: `point * jitter` spreads points sideways within a categorical band, but here both `x` and `y` are continuous — there is no band to spread within, and nudging a measured value would misplace it. For overplotting on two continuous axes, `style(opacity = )` reveals density without moving any point off its value.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
This is a design rule, not a missing feature: jitter moves a dot’s category, never its value. ggplot2’s geom_jitter() moves points along both axes by a small random amount, and gog does not. To show overlapping points in a scatterplot, keep every point at its value and change how it is drawn. style(opacity = ), which the message suggests, makes overlap read as a darker area, as in the plot of every gapminder year under Opacity. A hollow point, an outline with no fill, keeps every point visible where points overlap, as the same table shows under What you can set.
point * dodge is refused on a scatterplot for the same reason. It moves a dot only across a category’s slot, and a scatterplot has no slot:
data(gapminder_2007) + point * dodge + x(gdp) + y(life)data(gapminder_2007) + point * dodge + x(col.gdp) + y(col.life)data(gapminder_2007) + point * dodge + x(:gdp) + y(:life)plot(data(gapminder_2007), layer(point, dodge), x(col.gdp), y(col.life))Error:
! gog: `point * dodge` sets points apart inside a category's slot and moves them along the category axis only, but here neither `x` nor `y` is a category — there is no slot to swarm in, and moving a measured value would misplace it. For overplotting on two continuous axes, `style(opacity = )` reveals density without moving any point off its value.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
The fourth refusal is opacity on a hollow point. opacity fades what color paints, and color = "none" paints nothing, so there is nothing for it to fade:
data(gapminder_2007) + point + x(gdp) + y(life) +
style(color = "none", border_color = "steelblue", opacity = 0.3)(data(gapminder_2007) + point + x(col.gdp) + y(col.life) +
style(color = "none", border_color = "steelblue", opacity = 0.3))data(gapminder_2007) + point + x(:gdp) + y(:life) +
style(color = "none", border_color = "steelblue", opacity = 0.3)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
style({ color: "none", border_color: "steelblue", opacity: 0.3 }))Error:
! gog: `style(opacity = 0.3)` fades what `color` paints, and `color = "none"` paints nothing, so there is nothing here for it to fade. A border is drawn in its own color, so its transparency goes there: `style(color = "none", border_color = "#4682b44d")` draws a steelblue outline at 30%.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
The message suggests border_color = "#4682b44d" instead. In "#4682b44d" the transparency is part of the border’s own color, as in the hollow points under What you can set.
The fifth refusal is group on a scatterplot with no statistic. On a line, group cuts one stroke into several. Points are not joined into a stroke, and each row is already its own point, so splitting the rows by continent would change nothing:
data(gapminder_2007) + point + x(gdp) + y(life) + group(continent)data(gapminder_2007) + point + x(col.gdp) + y(col.life) + group(col.continent)data(gapminder_2007) + point + x(:gdp) + y(:life) + group(:continent)plot(data(gapminder_2007), point, x(col.gdp), y(col.life),
group(col.continent))Error:
! gog: `group` on a `point` needs `dodge` or a statistic to act on, and on this plot neither helps: `dodge` needs a category axis, and a statistic over `gdp` would summarize one row at a time. Each row is already its own point, so splitting the rows by `continent` would change nothing. To show the groups, map `continent` to `color` or `shape`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
gog accepts group on points only when a statistic or dodge is joined to them, as in Splitting without color. A scatterplot has no category axis for dodge to use, so the message offers the two channels that show the groups: map continent to color or shape instead.
8.11 In short
A point is a glyph at a position. The point mark draws one per row, unless a transform such as mean summarizes the rows first. It is the only mark that maps shape, because it is the only mark that picks its glyph from a fixed set. There are seven glyphs, and an eighth category gets the circle again.
8.12 What comes next
The point mark draws each row as a separate glyph, and nothing joins one row to the next. When one column puts the rows in order, such as years, the change from one value to the next often matters most. Line joins the rows into one stroke that shows whether the value rose or fell.