| 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 |
41 Two variables
You have two columns. What can they tell you together? Their types decide the question. Two measurements ask about relationship: do high values of one go with high values of the other? A category and a measurement ask about comparison: is the measurement larger in some groups than in others?
41.2 What is the trend through the cloud?
The scatter above is a cloud of 142 countries. The direction of that cloud is hard to judge from the dots alone. When the trend is the answer and every country still has to be seen, layer a derived line over the points. + puts both marks in one panel, and * smooth fits a curve through the middle of the cloud:
data(gapminder_2007) + x(gdp) + y(life) +
point + color(continent) +
line * smooth(data(gapminder_2007) + x(col.gdp) + y(col.life) +
point + color(col.continent) +
line * smooth)data(gapminder_2007) + x(:gdp) + y(:life) + point + color(:continent) +
line * smoothplot(data(gapminder_2007), x(col.gdp), y(col.life), point,
color(col.continent), layer(line, smooth))“Given gapminder 2007: x is gdp, y is life, points colored by continent, and also a line derived by smooth.”
The positions are written before any mark, so both layers share them; color is written after point, so only the points split. Transforms owns smooth; Encoding scope owns that rule.
41.3 How much was there at each time?
The actuals table holds one sales figure per year. Sometimes the question is not whether sales rose but how large they were in each year. A line says where the value went; an area says how much there was. It fills to zero, so the filled area is the amount:
| year | sales |
|---|---|
| 2019 | 120 |
| 2020 | 135 |
| 2021 | 128 |
| 2022 | 152 |
| 2023 | 168 |
data(actuals) + area + x(year) + y(sales)data(actuals) + area + x(col.year) + y(col.sales)data(actuals) + area + x(:year) + y(:sales)plot(data(actuals), area, x(col.year), y(col.sales))“Given the actuals: an area, x is year, y is sales.”
One atom away. If your column has no meaningful zero, you want line, not area. Owning chapters: Line, Area.
41.4 What is the typical value per group?
Each continent holds many countries, and each country has its own life expectancy. To compare the continents you need one number for each, so you summarize the measurement within each category. The aggregation family is sum, mean, median, max, min and quantile. Each one reads a bound y and reduces every group to a single number. quantile also needs a probability, written quantile(0.9):
data(gapminder_2007) + bar * mean + x(continent) + y(life)data(gapminder_2007) + bar * mean + x(col.continent) + y(col.life)data(gapminder_2007) + bar * mean + x(:continent) + y(:life)plot(data(gapminder_2007), layer(bar, mean), x(col.continent),
y(col.life))“Given gapminder 2007: bars derived by mean, x is continent, y is life.”
One atom away. The median is robust to outliers, so swap mean for median:
data(gapminder_2007) + bar * median + x(continent) + y(life)data(gapminder_2007) + bar * median + x(col.continent) + y(col.life)data(gapminder_2007) + bar * median + x(:continent) + y(:life)plot(data(gapminder_2007), layer(bar, median), x(col.continent),
y(col.life))“Given gapminder 2007: bars derived by median, x is continent, y is life.” Owning chapters: Bar, Transforms.
41.5 What does every value per group look like?
The bars above gave each continent one number, and that number hides how far its countries spread. Sometimes you want to see every country, not only the mean. The strip plot drops the derivation and changes the mark: point where bar * mean stood. Each row is one dot, at its own height in its category’s slot:
data(gapminder_2007) + point + x(continent) + y(life)data(gapminder_2007) + point + x(col.continent) + y(col.life)data(gapminder_2007) + point + x(:continent) + y(:life)plot(data(gapminder_2007), point, x(col.continent), y(col.life))“Given gapminder 2007: points, x is continent, y is life.”
When the dots pile onto one line, * jitter spreads them sideways, along the category axis only, so every dot keeps its exact height:
data(gapminder_2007) + point * jitter + x(continent) + y(life) + style(opacity = 0.6)data(gapminder_2007) + point * jitter + x(col.continent) + y(col.life) + style(opacity = 0.6)data(gapminder_2007) + point * jitter + x(:continent) + y(:life) +
style(opacity = 0.6)plot(data(gapminder_2007), layer(point, jitter), x(col.continent),
y(col.life), style({ opacity: 0.6 }))“Given gapminder 2007: points derived by jitter, x is continent, y is life.”
One atom away. Swap the positions and the strips lie down, useful when the category names are long. See Point and jitter.
41.6 What if the table already has the answer?
The medals table holds one row per country, and the question is how many bronze medals each one won. That count is already in the table, so no transform is involved; bar draws the numbers as they arrive:
| country | gold | silver | bronze |
|---|---|---|---|
| USA | 46 | 37 | 38 |
| China | 38 | 31 | 22 |
| Great Britain | 29 | 17 | 19 |
| Russia | 19 | 18 | 9 |
| Germany | 17 | 10 | 15 |
data(medals) + bar + x(country) + y(bronze)data(medals) + bar + x(col.country) + y(col.bronze)data(medals) + bar + x(:country) + y(:bronze)plot(data(medals), bar, x(col.country), y(col.bronze))“Given the medals: bars, x is country, y is bronze.”
One atom away. Ranked, because a comparison is easiest to read when it is sorted:
data(medals) + bar + x(country) + y(bronze) + order(bronze, desc = TRUE)data(medals) + bar + x(col.country) + y(col.bronze) + order(col.bronze, desc = True)data(medals) + bar + x(:country) + y(:bronze) +
order(:bronze, desc = true)plot(data(medals), bar, x(col.country), y(col.bronze),
order(col.bronze, { desc: true }))“Given the medals: bars, x is country, y is bronze, ordered by bronze, largest first.”
There is no hidden counting here to surprise you: if you want a computation, you write its name. That is why this recipe and How many of each? are different sentences.
41.7 What this section refuses
Suppose both columns are categorical, a country and its continent, and you write bar with one on each axis. A bar needs at least one of its axes to be a number: that number is the length of the bar. Two categories leave it nothing to measure, so the refusal names count:
data(gapminder_2007) + bar + x(country) + y(continent)data(gapminder_2007) + bar + x(col.country) + y(col.continent)data(gapminder_2007) + bar + x(:country) + y(:continent)plot(data(gapminder_2007), bar, x(col.country), y(col.continent))Error:
! gog: `bar` has categorical columns on both axes — `x(country)` and `y(continent)` — so there is nothing for it to measure. One axis must be a number: that is the length of the bar. To count rows per category instead, use `bar * count`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.