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:

gapminder_2007: first 5 of 142 rows
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
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))
Asia Europe Africa Americas Oceania 0 20 40 Count 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))
0 20 40 Oceania Americas Africa Europe Asia Continent Count

“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.2 What share of the whole is each?

In both count plots above, Africa has the longest bar: 52 countries. A count alone cannot say whether 52 is many or few. The table holds 142 countries in all, so Africa holds 52 of the 142, a share of about 0.37. When the question is each continent’s share of the whole, swap count for proportion. The counts become fractions that sum to one:

data(gapminder_2007) + bar * proportion + x(continent)
data(gapminder_2007) + bar * proportion + x(col.continent)
data(gapminder_2007) + bar * proportion + x(:continent)
plot(data(gapminder_2007), layer(bar, proportion), x(col.continent))
Asia Europe Africa Americas Oceania 0.0 0.1 0.2 0.3 Proportion Continent

“Given gapminder 2007: bars derived by proportion, x is continent.”

One atom away. Swap proportion back to count for the counts themselves. The bars above follow the order the continents first appear in the table. To draw them in another order, use order(), which sorts them by the values in a column of the table: order(continent) sorts them alphabetically, by their own names. The transform writes the share into its own output, not into your table, so order(proportion) finds no column to sort by and is refused. 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:

iris_flowers: first 5 of 150 rows
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))
2 4 6 0 10 20 30 40 Count 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))
2 4 6 0 10 20 Count 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))
0 2 4 6 8 0.0 0.1 0.2 Density 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)))
0 10 20 30 40 Count 2 4 6 0.0 0.1 0.2 Density 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))
2 4 6 0 10 20 30 40 Count 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:

gm_europe: first 5 of 30 rows
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"))
74 76 78 80 2 4 6 8 Thirty countries, thirty dots Count Life expectancy (years)

“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"))
74 76 78 80 0 2 4 6 8 The same thirty countries, summarized Count Life expectancy (years)

“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.