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:

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 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))
0 20 40 Oceania Americas Africa Europe Asia Continent Count

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

The count above shows 52 countries in Africa. That number means little until you know it is 52 out of 142. 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. Back to raw counts: * count. order() reorders the categories by a column the table holds, never by the invented share.

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:

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

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))
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.”

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))
40 60 80 0.00 0.01 0.02 0.03 0.04 Density 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))
50 60 70 80 0 10 20 30 Count 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:

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

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