| 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 |
40 One variable
You have one column. What can it tell you? The answer depends on the column’s type. A categorical column asks how many of each; a continuous column asks how the values spread.
40.1 How many of each?
Your column is categorical, one continent per country. The first question is how many countries each continent holds. Counting rows is a derivation, not a different kind of chart, so the sentence is bar with * count:
data(gapminder_2007) + bar * count + x(continent)data(gapminder_2007) + bar * count + x(col.continent)data(gapminder_2007) + bar * count + x(:continent)plot(data(gapminder_2007), layer(bar, count), x(col.continent))“Given gapminder 2007: bars derived by count, x is continent.” No y() is bound because the count is invented, and the engine labels the axis for you.
One atom away. Long category names? Move the categories to y and the bars lie down. The sentence is otherwise the same.
data(gapminder_2007) + bar * count + y(continent)data(gapminder_2007) + bar * count + y(col.continent)data(gapminder_2007) + bar * count + y(:continent)plot(data(gapminder_2007), layer(bar, count), y(col.continent))“Given gapminder 2007: bars derived by count, y is continent.”
The count still measures along the invented axis, which is now x. Owning chapters: Bar, Transforms.
40.3 How is a measurement distributed?
Your column is continuous, one petal length per flower. The first question is where the values cluster and how far they spread. bin cuts the axis into equal-width bins and counts the rows in each; bar draws them touching, so a gap means a bin with no rows. The result is the histogram:
| sepal_length | sepal_width | petal_length | species |
|---|---|---|---|
| 5.1 | 3.5 | 1.4 | setosa |
| 4.9 | 3.0 | 1.4 | setosa |
| 4.7 | 3.2 | 1.3 | setosa |
| 4.6 | 3.1 | 1.5 | setosa |
| 5.0 | 3.6 | 1.4 | setosa |
data(iris_flowers) + bar * bin + x(petal_length)data(iris_flowers) + bar * bin + x(col.petal_length)data(iris_flowers) + bar * bin + x(:petal_length)plot(data(iris_flowers), layer(bar, bin), x(col.petal_length))“Given the iris flowers: bars derived by bin, x is petal length.”
One atom away. bin takes a count or a width when the default does not suit the data. bin(30) asks for thirty bins; bin(width = 0.5) asks for bins half a unit wide:
data(iris_flowers) + bar * bin(width = 0.5) + x(petal_length)data(iris_flowers) + bar * bin(width = 0.5) + x(col.petal_length)data(iris_flowers) + bar * bin(width = 0.5) + x(:petal_length)plot(data(iris_flowers), layer(bar, bin({ width: 0.5 })),
x(col.petal_length))“Given the iris flowers: bars derived by bin at width 0.5, x is petal length.”
A line * bin is the frequency polygon: the same counts, joined through the bin centers. Owning chapters: Bar, Transforms.
40.4 What is its smooth shape?
A histogram’s shape depends on where its bin edges fall. A reader can mistake that choice for a feature of the data. When the shape of the distribution itself is the answer, the density estimate removes that decision:
data(gapminder_2007) + line * density + x(life)data(gapminder_2007) + line * density + x(col.life)data(gapminder_2007) + line * density + x(:life)plot(data(gapminder_2007), layer(line, density), x(col.life))“Given gapminder 2007: a line derived by density, x is life.” The same question the histogram answered, with one derivation changed. Draw both when the number of peaks is the thing you want to show. One atom tunes the smoothing: density(0.5) sharpens the peaks, density(2) flattens them, and density(bandwidth = 1) fixes the kernel width in years. Owning chapters: Line, Transforms.
40.5 How does the distribution look in steps?
A third mark on the same bin: a staircase outline, tracing the bin tops with no fill. It is most useful when several distributions share an axis, as in Many groups, and it reads well alone:
data(gapminder_2007) + step * bin + x(life)data(gapminder_2007) + step * bin + x(col.life)data(gapminder_2007) + step * bin + x(:life)plot(data(gapminder_2007), layer(step, bin), x(col.life))“Given gapminder 2007: a step outline derived by bin, x is life.”
Two marks (bar, step) and one bin, with line * bin above making three. The transform never changed. Only the geometry that draws its output changed. That is the No Exceptions law doing its job. Owning chapters: Step, Transforms.
40.6 When there are only thirty rows
Every answer above summarizes: bin needs a bin width, density needs a bandwidth, and both choices matter more when there are fewer rows to average over. With thirty numbers the bins can stay and every row still shows. Add stack and a point piles one dot per observation:
| country | continent | year | life | population | gdp |
|---|---|---|---|---|---|
| Albania | Europe | 2007 | 76.423 | 3600523 | 5937.030 |
| Austria | Europe | 2007 | 79.829 | 8199783 | 36126.493 |
| Belgium | Europe | 2007 | 79.441 | 10392226 | 33692.605 |
| Bosnia and Herzegovina | Europe | 2007 | 74.852 | 4552198 | 7446.299 |
| Bulgaria | Europe | 2007 | 73.005 | 7322858 | 10680.793 |
data(gm_europe) + point * bin * stack + x(life) +
x_label("Life expectancy (years)") + title("Thirty countries, thirty dots")(data(gm_europe) + point * bin * stack + x(col.life) +
x_label("Life expectancy (years)") + title("Thirty countries, thirty dots"))data(gm_europe) + point * bin * stack + x(:life) +
x_label("Life expectancy (years)") +
title("Thirty countries, thirty dots")plot(data(gm_europe), layer(point, bin, stack), x(col.life),
x_label("Life expectancy (years)"),
title("Thirty countries, thirty dots"))“Given gapminder Europe: points derived by bin and stack, x is life.” The pile’s height is a count, so the tallest pile can be read off the axis or counted dot by dot. No row has been averaged into anything. Compare the same thirty as bars, where each pile becomes one length and the rows disappear into it:
data(gm_europe) + bar * bin + x(life) +
x_label("Life expectancy (years)") + title("The same thirty, summarized")(data(gm_europe) + bar * bin + x(col.life) +
x_label("Life expectancy (years)") + title("The same thirty, summarized"))data(gm_europe) + bar * bin + x(:life) +
x_label("Life expectancy (years)") +
title("The same thirty, summarized")plot(data(gm_europe), layer(bar, bin), x(col.life),
x_label("Life expectancy (years)"),
title("The same thirty, summarized"))“Given gapminder Europe: bars derived by bin, x is life.”
One atom away. A categorical column instead: point * count * stack piles one dot per row in each category. Owning chapter: Transforms.
40.7 What this section refuses
A distribution question needs a continuous column. Give density a category and the refusal names the column type it needs. The categorical version of this question is the share recipe above:
data(gapminder_2007) + line * density + x(continent)data(gapminder_2007) + line * density + x(col.continent)data(gapminder_2007) + line * density + x(:continent)plot(data(gapminder_2007), layer(line, density), x(col.continent))Error:
! gog: `density` estimates a continuous distribution, and `x(continent)` is categorical — there is no number line for the curve to spread along. For the share of rows in each category, that is `bar * proportion`.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.