| 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? Which question you can ask depends on the column’s type. With a categorical column you ask how many rows fall in each category. With a continuous column you ask how the values spread. When the table is small, there is a third question: whether to summarize the rows at all.
40.1 How many of each?
In gapminder_2007 the continent 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. 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 count writes the y column itself. The engine labels that axis for you.
One atom away. When the category names are long, put them on y instead. The bars are then horizontal, and 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 axis has moved to x, so each bar’s length is read from left to right. Owning chapters: Bar, Transforms.
40.3 How is a measurement distributed?
The two recipes above counted categories. With a continuous column you ask a different question. In the iris_flowers table, petal_length is continuous, one measurement 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 those counts as touching rectangles, so a gap is 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.” The gap near 2.5 cm is an empty bin. No flower in the table has a petal length between 1.9 and 3.0 cm. One species has much shorter petals than the other two, and the empty bin is part of the space between them.
One atom away. When the automatic choice makes the shape hard to read, give bin a number of bins or a bin width. bin(30) asks for thirty bins; bin(width = 0.5) asks for bins 0.5 cm 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.”
The width 0.5 suits these lengths: they are recorded to one decimal place, so every bin holds exactly five possible values. Choosing a width explains how to pick a width for your own column.
A line * bin is the frequency polygon: a line through the same counts, placed at the center of each bin. Owning chapters: Bar, Transforms.
40.4 What is its smooth shape?
A histogram’s shape depends on the choice of bin edges. A reader can mistake that choice for a feature of the data. When the shape of the distribution itself is the answer, density draws a curve with no bin edges at all. Here is the histogram’s own column again, the petal lengths:
data(iris_flowers) + line * density + x(petal_length)data(iris_flowers) + line * density + x(col.petal_length)data(iris_flowers) + line * density + x(:petal_length)plot(data(iris_flowers), layer(line, density), x(col.petal_length))“Given the iris flowers: a line derived by density, x is petal length.” This answers the question the histogram answered, for the same column, with density in place of bin and a line to draw it. The two groups of petals are now two peaks, and the valley between them lies over the empty bin.
The curve starts below zero, although no petal is shorter than 1 cm. A density spreads each row into a small smooth bump, and the bumps of the shortest petals reach past zero.
The width of each bump is the bandwidth, and it sets how smooth the curve is. A number written alone multiplies the default bandwidth: density(0.5) is half as smooth as the default and density(2) is twice as smooth. density(bandwidth = 0.5) sets the bandwidth itself instead, in the column’s own unit, so here it is half a centimeter. Write one of the two forms, never both.
A histogram and a density curve can show a different number of peaks, because a wide bin or a wide bandwidth can merge two peaks into one. When the number of peaks matters, draw the histogram and the curve together. Put each in a plot of its own, and join the two plots with /, which places the first above the second:
(data(iris_flowers) + bar * bin + x(petal_length)) /
(data(iris_flowers) + line * density + x(petal_length))((data(iris_flowers) + bar * bin + x(col.petal_length)) /
(data(iris_flowers) + line * density + x(col.petal_length)))(data(iris_flowers) + bar * bin + x(:petal_length)) /
(data(iris_flowers) + line * density + x(:petal_length))below(plot(data(iris_flowers), layer(bar, bin), x(col.petal_length)),
plot(data(iris_flowers), layer(line, density), x(col.petal_length)))“Given the iris flowers: bars derived by bin, x is petal length, above a line derived by density, x is petal length.”
The two plots read the same column, so they share one x axis. The curve’s two peaks are below the two groups of bars, so here the two groups are not an effect of the bins or of the bandwidth. Each plot keeps its own y axis. A bar counts the flowers in its bin, and the curve’s height is the share of flowers per centimeter of petal length.
In this arrangement, the curve is cut at the shortest and the longest petal. So it begins at 1 cm, high in its panel, rather than below zero. The marginal plot explains why a shared axis cuts it.
You might first try to draw the curve over the bars, in one panel. The tallest bar counts 44 flowers, and the curve never rises above 0.3, so on one axis the curve would lie flat along the bottom. gog refuses the sentence:
data(iris_flowers) + bar * bin + line * density + x(petal_length)data(iris_flowers) + bar * bin + line * density + x(col.petal_length)data(iris_flowers) + bar * bin + line * density + x(:petal_length)plot(data(iris_flowers), layer(bar, bin), layer(line, density),
x(col.petal_length))Error:
! gog: `bar * bin` draws counts on `y`, and `line * density` draws density estimates there. An axis holds one kind of number, so one layer would be drawn in the other's units. Give each its own plot, the second below the first with `/`, so that each `y` axis measures one thing.
gog: nothing was rendered. Fix the above, or set GOG_STRICT=0 to draw anyway.
A share histogram, bar * bin * proportion, is refused in one panel with the curve too, because a share is not a density either. Owning chapters: Line, Transforms, Composition.
40.5 How does the distribution look as a staircase?
The histogram drew bin with bar. step can draw it too, tracing the bin tops as a staircase outline with no fill. It is most useful when several distributions share one axis, as in Many groups, because unfilled outlines do not cover each other. This plot draws the histogram’s distribution again, so you can compare the two marks directly:
data(iris_flowers) + step * bin + x(petal_length)data(iris_flowers) + step * bin + x(col.petal_length)data(iris_flowers) + step * bin + x(:petal_length)plot(data(iris_flowers), layer(step, bin), x(col.petal_length))“Given the iris flowers: a step outline derived by bin, x is petal length.”
This is the histogram’s sentence with one atom changed, step in place of bar. The bins are the same, the empty one included, and only the mark that draws them changed. With the line * bin named earlier, that is three marks on one transform. That is the No Exceptions law: a transform behaves the same on every mark. Owning chapters: Step, Transforms.
40.6 When there are only thirty rows
Every answer above replaces the rows with a summary. bin needs a bin width, and density needs a bandwidth. With few rows, either choice can suggest structure the data does not have. The gm_europe table holds the thirty European countries of 2007, and thirty rows are few enough to draw one by one. bin still cuts the axis, and adding stack piles one point per row. The result is the dot plot:
| 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.” Each pile’s height is a count. You can read the tallest pile’s height from the axis, or count its dots one by one. No row has been replaced by a summary. Compare the same thirty countries as bars, where each pile becomes one bar and the individual rows are no longer drawn:
data(gm_europe) + bar * bin + x(life) + x_label("Life expectancy (years)") +
title("The same thirty countries, summarized")(data(gm_europe) + bar * bin + x(col.life) + x_label("Life expectancy (years)") +
title("The same thirty countries, summarized"))data(gm_europe) + bar * bin + x(:life) +
x_label("Life expectancy (years)") +
title("The same thirty countries, summarized")plot(data(gm_europe), layer(bar, bin), x(col.life),
x_label("Life expectancy (years)"),
title("The same thirty countries, summarized"))“Given gapminder Europe: bars derived by bin, x is life.”
One atom away. For a categorical column, point * count * stack piles one dot per row in each category. Owning chapter: Transforms.
40.7 What this section refuses
Every recipe above that used density or bin gave it a continuous column. Suppose you give density the continent column instead. density estimates a continuous distribution, so gog refuses the sentence. For a categorical column, use the share recipe in What share of the whole is each?. The message says which kind of column density needs, and it names the same fix, bar * proportion:
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.